mDNSResponder-2559.1.1

Imported from mDNSResponder-2559.1.1.tar.gz
diff --git a/Clients/dns-sd.c b/Clients/dns-sd.c
index 5051aa3..f0308d6 100644
--- a/Clients/dns-sd.c
+++ b/Clients/dns-sd.c
@@ -2255,7 +2255,11 @@
     case 'Q':
     case 'C':   {
         uint16_t rrtype, rrclass;
-        flags |= kDNSServiceFlagsReturnIntermediates;
+        // For the exitWhenNoMoreComing test case, we do not want intermediates, since this would result in
+        // a premature exit.
+        if (!exitWhenNoMoreComing) {
+            flags |= kDNSServiceFlagsReturnIntermediates;
+        }
         if (operation == 'q')
             flags |= kDNSServiceFlagsSuppressUnusable;
         if (enable_dnssec)
diff --git a/Clients/dnssdutil/DNSServerDNSSEC.c b/Clients/dnssdutil/DNSServerDNSSEC.c
index 9331613..26e334f 100644
--- a/Clients/dnssdutil/DNSServerDNSSEC.c
+++ b/Clients/dnssdutil/DNSServerDNSSEC.c
@@ -1,6 +1,18 @@
 /*
-	Copyright (c) 2020 Apple Inc. All rights reserved.
-*/
+ * Copyright (c) 2020-2023 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
 
 #include "DNSServerDNSSEC.h"
 
@@ -3453,7 +3465,7 @@
 			require_quiet( digestToSignRef != NULL, ecdsa_sign_exit);
 
 			// Sign the data
-			sigDataRef = SecKeyCreateSignature( secKeyRef, kSecKeyAlgorithmECDSASignatureRFC4754, digestToSignRef, &cfError );
+			sigDataRef = SecKeyCreateSignature( secKeyRef, kSecKeyAlgorithmECDSASignatureDigestRFC4754, digestToSignRef, &cfError );
 			require_quiet( sigDataRef != NULL, ecdsa_sign_exit );
 
 			// copy the result
@@ -3571,7 +3583,7 @@
 			{
 				memcpy( pubKeyDataWithPrefix + 1, inKeyInfo->ECDSAP384SHA384.pubKey, sizeof( inKeyInfo->ECDSAP384SHA384.pubKey ) );
 			}
-			verifyAlgorithm = kSecKeyAlgorithmECDSASignatureRFC4754;
+			verifyAlgorithm = kSecKeyAlgorithmECDSASignatureDigestRFC4754;
 
 			pubKeyDic = CFDictionaryCreate( kCFAllocatorDefault, pubKeyRefOpts, pubKeyRefVals,
 				sizeof( pubKeyRefOpts ) / sizeof( void * ), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks );
diff --git a/Clients/dnssdutil/TestUtils.m b/Clients/dnssdutil/TestUtils.m
index f0cd43d..f9ed2b6 100644
--- a/Clients/dnssdutil/TestUtils.m
+++ b/Clients/dnssdutil/TestUtils.m
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019-2022 Apple Inc. All rights reserved.
+ * Copyright (c) 2019-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,29 +19,6 @@
 #import <dlfcn.h>
 #import <XCTest/XCTest.h>
 
-#if TARGET_OS_OSX
-#define XCTest_Framework_Runtime_Path       "/AppleInternal/Developer/Library/Frameworks/XCTest.framework/XCTest"
-#else
-#define XCTest_Framework_Runtime_Path       "/Developer/Library/Frameworks/XCTest.framework/XCTest"
-#endif
-
-//===========================================================================================================================
-//    XCTest Utils
-//===========================================================================================================================
-static bool _load_xctest_framework(void)
-{
-    bool loaded = (NSClassFromString(@"XCTestSuite") != nil);
-    static void *s_xctest_handle;
-    if (!loaded) {
-        s_xctest_handle = dlopen(XCTest_Framework_Runtime_Path, RTLD_LAZY | RTLD_LOCAL);
-        loaded = (NSClassFromString(@"XCTestSuite") != nil);
-        if (!loaded) {
-            fprintf(stderr, "%s Failed to load XCTest framework from: %s\n", __FUNCTION__, XCTest_Framework_Runtime_Path);
-        }
-    }
-    return loaded;
-}
-
 //===========================================================================================================================
 //    Main Test Running
 //===========================================================================================================================
@@ -49,20 +26,18 @@
 bool run_xctest_named(const char *classname)
 {
     bool result = false;
-    if (_load_xctest_framework()) {
-        NSString *name = [NSString stringWithUTF8String:classname];
-        NSBundle *testBundle = [NSBundle bundleWithPath:@"/AppleInternal/XCTests/com.apple.mDNSResponder/Tests.xctest"];
-        [testBundle load];
+	NSString *name = [NSString stringWithUTF8String:classname];
+	NSBundle *testBundle = [NSBundle bundleWithPath:@"/AppleInternal/XCTests/com.apple.mDNSResponder/Tests.xctest"];
+	[testBundle load];
 
-        XCTestSuite *compiledSuite = [NSClassFromString(@"XCTestSuite") testSuiteForTestCaseWithName: name];
-        if (compiledSuite.tests.count) {
-            [compiledSuite runTest];
-            XCTestRun *testRun = compiledSuite.testRun;
-            result = (testRun.hasSucceeded != NO);
-        } else {
-            fprintf(stderr, "%s Test class %s not found\n", __FUNCTION__, classname);
-        }
-    }
+	XCTestSuite *compiledSuite = [NSClassFromString(@"XCTestSuite") testSuiteForTestCaseWithName: name];
+	if (compiledSuite.tests.count) {
+		[compiledSuite runTest];
+		XCTestRun *testRun = compiledSuite.testRun;
+		result = (testRun.hasSucceeded != NO);
+	} else {
+		fprintf(stderr, "%s Test class %s not found\n", __FUNCTION__, classname);
+	}
     return result;
 }
 
diff --git a/Clients/dnssdutil/dnssdutil.c b/Clients/dnssdutil/dnssdutil.c
index 18cf6d1..efc1c8f 100644
--- a/Clients/dnssdutil/dnssdutil.c
+++ b/Clients/dnssdutil/dnssdutil.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2016-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -64,7 +64,6 @@
 
 #if( MDNSRESPONDER_PROJECT )
 	#include <CoreFoundation/CFXPCBridge.h>
-	#include <dns_services.h>
 	#include "dnssd_private.h"
 	#include <mdns/private.h>
 	#include <mdns/tcpinfo.h>
@@ -271,6 +270,39 @@
 	#define nw_forget( X )		ForgetCustom( X, nw_release )
 #endif
 
+// When running a Do53 or DoT server for testing on tvOS and iOS, use an alternate server port instead of the standard
+// UDP/TCP server ports of 53 and 853 respectively, which may be in use by the Unicast Bonjour discovery proxy.
+
+#if( TARGET_OS_TV || TARGET_OS_IOS || TARGET_OS_OSX )
+	#define DNSSDUTIL_TEST_USE_ALTERNATE_SERVER_PORT_FOR_DO53		1
+	#define DNSSDUTIL_TEST_USE_ALTERNATE_SERVER_PORT_FOR_DOT		1
+#else
+	#define DNSSDUTIL_TEST_USE_ALTERNATE_SERVER_PORT_FOR_DO53		0
+	#define DNSSDUTIL_TEST_USE_ALTERNATE_SERVER_PORT_FOR_DOT		0
+#endif
+
+// Most tests that spawn a DNS server for their testing don't actually care about the port used by the DNS protocol, so
+// when we need to use an alternate listening port, just use a random ephemeral port (--port 0). If an alternate
+// listening port isn't necessary, just use the default port, which is the common case.
+
+#if( DNSSDUTIL_TEST_USE_ALTERNATE_SERVER_PORT_FOR_DO53 )
+	#define DNSSDUTIL_TEST_DNS_SERVER_COMMAND_PREAMBLE		"dnssdutil server --port 0"
+#else
+	#define DNSSDUTIL_TEST_DNS_SERVER_COMMAND_PREAMBLE		"dnssdutil server"
+#endif
+
+// When it's necessary to use a specific alternate port for Do53 for testing, use port 202, which is the AppleTalk Name
+// Binding UDP/TCP port (https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml).
+// AppleTalk is discontinued, so it should be OK to squat on one of its ports, at least for testing.
+
+#define kDNSPort_Do53Alt		202
+
+// IDs and keys for mDNSResponder preferences. See mDNSResponder(8) man page.
+
+#define kMDNSResponderPrefAppIDStr		"com.apple.mDNSResponder"
+
+#define kMDNSResponderPrefStr_AlwaysAppendSearchDomains		"AlwaysAppendSearchDomains"
+
 //===========================================================================================================================
 //	Gerneral Command Options
 //===========================================================================================================================
@@ -580,13 +612,14 @@
 //	QueryRecord Command Options
 //===========================================================================================================================
 
-static const char *		gQueryRecord_Name			= NULL;
-static const char *		gQueryRecord_Type			= NULL;
-static int				gQueryRecord_AAAAFallback	= false;
-static int				gQueryRecord_UseFailover	= false;
-static int				gQueryRecord_OneShot		= false;
-static int				gQueryRecord_TimeLimitSecs	= 0;
-static int				gQueryRecord_RawRData		= false;
+static const char *		gQueryRecord_Name				= NULL;
+static const char *		gQueryRecord_Type				= NULL;
+static int				gQueryRecord_AAAAFallback		= false;
+static int				gQueryRecord_UseFailover		= false;
+static const char *		gQueryRecord_ResolverOverride	= NULL;
+static int				gQueryRecord_OneShot			= false;
+static int				gQueryRecord_TimeLimitSecs		= 0;
+static int				gQueryRecord_RawRData			= false;
 
 static CLIOption		kQueryRecordOpts[] =
 {
@@ -610,8 +643,9 @@
 	DNSSDFlagsOption_UnicastResponse(),
 	
 	CLI_OPTION_GROUP( "Attributes" ),
-	BooleanOption( 0, "aaaaFallback",	&gQueryRecord_AAAAFallback,		"If a AAAA record doesn't exist, try querying for an A record of the same name and type." ),
-	BooleanOption( 0, "useFailover",	&gQueryRecord_UseFailover,		"Use DNS service failover if necessary and applicable." ),
+	BooleanOption( 0, "aaaaFallback",     &gQueryRecord_AAAAFallback,     "If a AAAA record doesn't exist, try querying for an A record of the same name and type." ),
+	BooleanOption( 0, "useFailover",      &gQueryRecord_UseFailover,      "Use DNS service failover if necessary and applicable." ),
+	StringOption(  0, "resolverOverride", &gQueryRecord_ResolverOverride, "UUID", "UUID of libnetwork resolver configuration to use as override.", false ),
 	
 	CLI_OPTION_GROUP( "Operation" ),
 	ConnectionOptions(),
@@ -638,6 +672,7 @@
 static const char *			gRegister_TXT			= NULL;
 static int					gRegister_LifetimeMs	= -1;
 static const char *			gRegister_TimeOfReceipt	= NULL;
+static const char *			gRegister_HostKeyHash	= NULL;
 
 static const char **		gAddRecord_Types		= NULL;
 static size_t				gAddRecord_TypesCount	= 0;
@@ -663,8 +698,9 @@
 	StringOption(   0 , "txt",		&gRegister_TXT,		"record data",	"The TXT record data. See " kRecordDataSection_Name " below.", false ),
 	
 	CLI_OPTION_GROUP( "Attributes" ),
-	StringOption( 0, "timestamp", &gRegister_TimeOfReceipt, "Unix time", "Time since epoch in seconds to indicate when the service registration request is received, should be used with flag kDNSServiceFlagsNoAutoRename", false ),
-	
+	StringOption( 0, "timestamp",	&gRegister_TimeOfReceipt,	"Unix time",	"Time since epoch in seconds to indicate when the service registration request is received, should be used with flag kDNSServiceFlagsNoAutoRename", false ),
+	StringOption( 0, "hostKeyHash",	&gRegister_HostKeyHash,		"32-bit hash",	"Unique hostkey hash value.", false ),
+
 	CLI_OPTION_GROUP( "Flags" ),
 	DNSSDFlagsOption(),
 	DNSSDFlagsOption_IncludeAWDL(),
@@ -716,6 +752,7 @@
 static int				gRegisterRecord_UpdateDelayMs	= 0;
 static int				gRegisterRecord_UpdateTTL		= 0;
 static const char *		gRegisterRecord_TimeOfReceipt	= NULL;
+static const char *		gRegisterRecord_HostKeyHash		= NULL;
 
 static CLIOption		kRegisterRecordOpts[] =
 {
@@ -726,8 +763,9 @@
 	IntegerOption( 0 , "ttl",	&gRegisterRecord_TTL,	"seconds",		"Time-to-live in seconds. Use '0' for default.", false ),
 	
 	CLI_OPTION_GROUP( "Attributes" ),
-	StringOption( 0, "timestamp", &gRegisterRecord_TimeOfReceipt, "Unix time", "Time since epoch in seconds to indicate when the record registration request is received.", false ),
-	
+	StringOption( 0, "timestamp",	&gRegisterRecord_TimeOfReceipt,	"Unix time",	"Time since epoch in seconds to indicate when the record registration request is received.", false ),
+	StringOption( 0, "hostKeyHash",	&gRegisterRecord_HostKeyHash,	"32-bit hash",	"Unique hostkey hash value.", false ),
+
 	CLI_OPTION_GROUP( "Flags" ),
 	DNSSDFlagsOption(),
 	DNSSDFlagsOption_ForceMulticast(),
@@ -1050,9 +1088,19 @@
 
 static CLIOption		kDNSQueryOpts[] =
 {
-	StringOption(  'n', "name",             &gDNSQuery_Name,             "name", "Question name (QNAME) to put in DNS query message.", true ),
-	StringOption(  't', "type",             &gDNSQuery_Type,             "type", "Question type (QTYPE) to put in DNS query message. (default: A)", false ),
-	StringOption(  's', "server",           &gDNSQuery_Server,           "IP address", "DNS server's IPv4 or IPv6 address.", kDNSQueryServerOptionIsRequired ),
+	StringOption(   'n', "name",             &gDNSQuery_Name,             "name", "Question name (QNAME) to put in DNS query message.", true ),
+	StringOption(   't', "type",             &gDNSQuery_Type,             "type", "Question type (QTYPE) to put in DNS query message. (default: A)", false ),
+	StringOptionEx( 's', "server",           &gDNSQuery_Server,           "IP address[+port]", "DNS server's IPv4 or IPv6 address and optional port.", kDNSQueryServerOptionIsRequired,
+		"The following exemplify the notations that are supported:\n"
+		"\n"
+		"    IPv4 address without port:     192.0.2.1\n"
+		"    IPv4 address with port 50001:  192.0.2.1:50001\n"
+		"    IPv6 address without port:     2001:db8::1\n"
+		"    IPv6 address with port 50001:  [2001:db8::1]:50001\n"
+		"\n"
+		"If no port is specified, then the default DNS port of 53 is assumed.\n"
+		"\n"
+	),
 	IntegerOption( 'l', "timeLimit",        &gDNSQuery_TimeLimitSecs,    "seconds", "Specifies query time limit. Use '-1' for no limit and '0' to exit immediately after sending.", false ),
 	BooleanOption(  0 , "tcp",              &gDNSQuery_UseTCP,           "Send the DNS query via TCP instead of UDP." ),
 	IntegerOption( 'f', "flags",            &gDNSQuery_Flags,            "flags", "16-bit value for DNS header flags/codes field. (default: 0x0100 [Recursion Desired])", false ),
@@ -1475,6 +1523,8 @@
 static const char *		gDNSServer_FollowPID			= NULL;
 static int				gDNSServer_ExtraV6Count			= 0;
 static const char *		gDNSServer_Protocol				= kDNSProtocolStr_Do53;
+static int				gDNSServer_RegisterWithSC		= false;
+static int				gDNSServer_MatchAllDomains		= false;
 
 static CLIOption		kDNSServerOpts[] =
 {
@@ -1482,7 +1532,7 @@
 	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 ),
+	IntegerOption(     'p', "port",          &gDNSServer_Port,            "port number", "UDP/TCP listening port. Use 0 for an ephemeral port. (default: Do53 → 53, DoT → 853, DoH → 443)", false ),
 	StringOption(       0 , "domain",        &gDNSServer_DomainOverride,  "domain", "Use to override 'd.test.' as the server's domain.", false ),
 	MultiStringOption( 'i', "ignoreQType",	 &gDNSServer_IgnoredQTypes, &gDNSServer_IgnoredQTypesCount, "qtype", "A QTYPE to ignore. This option can be specified more than once.", false ),
 	BooleanOption(      0 , "ipv4",          &gDNSServer_ListenOnV4,      "Listen on IPv4. Will listen on both IPv4 and IPv6 if neither --ipv4 nor --ipv6 is used." ),
@@ -1497,6 +1547,8 @@
 		"Use '" kDNSProtocolStr_DoT  "' for DNS over TLS (DoT).\n"
 		"Use '" kDNSProtocolStr_DoH  "' for DNS over HTTPS (DoH).\n"
  	),
+	BooleanOption(     's', "registerSC",    &gDNSServer_RegisterWithSC,  "Register Do53 service with SystemConfiguration instead of mrc_dns_service_registration_*." ),
+	BooleanOption(      0 , "default",       &gDNSServer_MatchAllDomains, "If registering Do53 service with SystemConfiguration, include '.' as a match domain." ),
 #if( TARGET_OS_DARWIN )
 	CLI_OPTION_GROUP( "Loopback-Only Mode Options" ),
 	IntegerOptionEx( 0 , "extraIPv6",     &gDNSServer_ExtraV6Count,    "count", "The number of extra IPv6 addresses to listen on. (default: 0)", false,
@@ -2054,14 +2106,12 @@
 
 static const char *		gDNSProxyTest_OutputFormat		= kOutputFormatStr_JSON;
 static const char *		gDNSProxyTest_OutputFilePath	= NULL;
-static int				gDNSProxyTest_UseLegacyDNSProxy	= false;
 
 static CLIOption		kDNSProxyTestOpts[] =
 {
 	CLI_OPTION_GROUP( "Results" ),
 	FormatOption(  'f', "format", &gDNSProxyTest_OutputFormat,      "Specifies the test results output format. (default: " kOutputFormatStr_JSON ")", false ),
 	StringOption(  'o', "output", &gDNSProxyTest_OutputFilePath,    "path", "Path of the file to write test results to instead of standard output (stdout).", false ),
-	BooleanOption( 'l', "legacy", &gDNSProxyTest_UseLegacyDNSProxy, "Use the legacy DNS proxy client SPI, i.e., DNSXEnableProxy() and DNSXEnableProxy64()." ),
 	
 	TestExitStatusSection(),
 	CLI_OPTION_END()
@@ -2170,27 +2220,69 @@
 	CLI_OPTION_END()
 };
 
+static void	OptimisticDNSTestCommand( void );
+
+static int		gOptimisticDNSTest_FullTest = false;
+
+static CLIOption		kOptimisticDNSTestOpts[] =
+{
+	BooleanOption( 'f', "full", &gOptimisticDNSTest_FullTest, "Proceed with the full version of the test, including subtests that require mDNSResponder to be killed." ),
+	CLI_SECTION( "Exit Status", "This command exits with a status code of 0 if the test passed and a non-zero status code if it fails.\n" ),
+	CLI_OPTION_END()
+};
+
+static void	RecordRegistrationTestCommand( void );
+
+static int		gRecordRegistrationTest_RRSetChangeIntervalMs = 0;
+
+static CLIOption		kRecordRegistrationTestOpts[] =
+{
+	IntegerOption( 'n', "interval", &gRecordRegistrationTest_RRSetChangeIntervalMs, "ms", "Interval between bulk RRSet changes in milliseconds. Use 0 for a default interval. (default: 0)", false ),
+	CLI_SECTION( "Exit Status", "This command exits with a status code of 0 if the test passed and a non-zero status code if it fails.\n" ),
+	CLI_OPTION_END()
+};
+
+static void	RecordCacheFlushTestCommand( void );
+
+static CLIOption		kRecordCacheFlushTestOpts[] =
+{
+	CLI_SECTION( "Exit Status", "This command exits with a status code of 0 if the test passed and a non-zero status code if it fails.\n" ),
+	CLI_OPTION_END()
+};
+
+static void	ResolverOverrideTestCommand( void );
+
+static CLIOption		kResolverOverrideTestOpts[] =
+{
+	CLI_SECTION( "Exit Status", "This command exits with a status code of 0 if the test passed and a non-zero status code if it fails.\n" ),
+	CLI_OPTION_END()
+};
+
 static CLIOption		kTestOpts[] =
 {
-	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 ),
-	Command( "registration",   RegistrationTestCmd,  kRegistrationTestOpts,   "Tests service registrations.", 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 ),
+	Command( "registration",                  RegistrationTestCmd,           kRegistrationTestOpts,         "Tests service registrations.", false ),
 #if( MDNSRESPONDER_PROJECT )
-	Command( "fallback",       FallbackTestCmd,      kFallbackTestOpts,       "Tests DNS server fallback.", false ),
-    Command( "expensive_constrained_updates", ExpensiveConstrainedTestCmd, kExpensiveConstrainedTestOpts, "Tests if the mDNSResponder can handle expensive and constrained property change correctly", false),
-	Command( "dnsproxy",       DNSProxyTestCmd,      kDNSProxyTestOpts,       "Tests mDNSResponder's DNS proxy.", false ),
-	Command( "rcodes",         RCodeTestCmd,         kRCodeTestOpts,          "Tests handling of all DNS RCODEs.", false ),
-	Command( "dnsquery",       DNSQueryTestCmd,      kDNSQueryTestOpts,       "Tests mDNSResponder's DNS queries.", false ),
-	Command( "fastrecovery",   FastRecoveryTestCmd,  kFastRecoveryTestOpts,   "Tests mDNSResponder's fast querier recovery.", false ),
-    Command( "xctest",         XCTestCmd,            kXCTestOpts,			  "Run a XCTest from /AppleInternal/XCTests/com.apple.mDNSResponder/Tests.xctest.", true ),
-	Command( "multiconnect",   MultiConnectTestCmd,	 kMultiConnectTestOpts,	  "Tests multiple simultanious connections.", false ),
+	Command( "fallback",                      FallbackTestCmd,               kFallbackTestOpts,             "Tests DNS server fallback.", false ),
+	Command( "expensive_constrained_updates", ExpensiveConstrainedTestCmd,   kExpensiveConstrainedTestOpts, "Tests if the mDNSResponder can handle expensive and constrained property change correctly", false ),
+	Command( "dnsproxy",                      DNSProxyTestCmd,               kDNSProxyTestOpts,             "Tests mDNSResponder's DNS proxy.", false ),
+	Command( "rcodes",                        RCodeTestCmd,                  kRCodeTestOpts,                "Tests handling of all DNS RCODEs.", false ),
+	Command( "dnsquery",                      DNSQueryTestCmd,               kDNSQueryTestOpts,             "Tests mDNSResponder's DNS queries.", false ),
+	Command( "fastrecovery",                  FastRecoveryTestCmd,           kFastRecoveryTestOpts,         "Tests mDNSResponder's fast querier recovery.", false ),
+	Command( "xctest",                        XCTestCmd,                     kXCTestOpts,                   "Run a XCTest from /AppleInternal/XCTests/com.apple.mDNSResponder/Tests.xctest.", true ),
+	Command( "multiconnect",                  MultiConnectTestCmd,           kMultiConnectTestOpts,         "Tests multiple simultanious connections.", false ),
 #endif
 #if( TARGET_OS_DARWIN )
-	Command( "keepalive",      KeepAliveTestCmd,     kKeepAliveTestOpts,      "Tests keepalive record registrations.", false ),
+	Command( "keepalive",                     KeepAliveTestCmd,              kKeepAliveTestOpts,            "Tests keepalive record registrations.", false ),
 #endif
-	Command( "dnssec",         DNSSECTestCmd,        kDNSSECTestOpts,         "Tests mDNSResponder's DNSSEC validation", false),
+	Command( "dnssec",                        DNSSECTestCmd,                 kDNSSECTestOpts,               "Tests mDNSResponder's DNSSEC validation", false),
+	Command( "optimisticDNS",                 OptimisticDNSTestCommand,      kOptimisticDNSTestOpts,        "Tests mDNSResponder's Optimistic DNS functionality.", false ),
+	Command( "record-registration",           RecordRegistrationTestCommand, kRecordRegistrationTestOpts,   "Tests the registration and deregistration of records.", false ),
+	Command( "record-cache-flush",            RecordCacheFlushTestCommand,   kRecordCacheFlushTestOpts,     "Tests the mrc_record_cache_flush SPI.", false ),
+	Command( "resolver-override",             ResolverOverrideTestCommand,   kResolverOverrideTestOpts,     "Tests use of DNSServiceQueryRecordWithAttribute() with a resolver override attribute.", false ),
 	CLI_OPTION_END()
 };
 
@@ -2675,6 +2767,42 @@
 };
 
 //===========================================================================================================================
+//	WiFi
+//===========================================================================================================================
+
+static void	WiFiOnCommand( void );
+static void	WiFiOffCommand( void );
+
+static CLIOption		kWiFiOpts[] =
+{
+	Command( "on",  WiFiOnCommand,  NULL, "Turn Wi-Fi power on.", true ),
+	Command( "off", WiFiOffCommand, NULL, "Turn Wi-Fi power off.", true ),
+	CLI_OPTION_END()
+};
+
+//===========================================================================================================================
+//	Discovery Proxy
+//===========================================================================================================================
+
+static void	DiscoveryProxyCommand( void );
+
+static char **		gDiscoveryProxy_ServerAddrs			= NULL;
+static size_t		gDiscoveryProxy_ServerAddrCount		= 0;
+static char **		gDiscoveryProxy_MatchDomains		= NULL;
+static size_t		gDiscoveryProxy_MatchDomainCount	= 0;
+static char **		gDiscoveryProxy_Certificates		= NULL;
+static size_t		gDiscoveryProxy_CertificateCount	= 0;
+
+static CLIOption		kDiscoveryProxyOpts[] =
+{
+	InterfaceOption(),
+	MultiStringOption( 's', "server",		&gDiscoveryProxy_ServerAddrs, &gDiscoveryProxy_ServerAddrCount,		"IP address",	"Server's IPv4 or IPv6 address with optionally-specified port. Can be specified more than once.", true ),
+	MultiStringOption( 'd', "domain",		&gDiscoveryProxy_MatchDomains, &gDiscoveryProxy_MatchDomainCount,	"domain name",	"Domain to match. Can be specified more than once.", true ),
+	MultiStringOption( 'c', "certificate",	&gDiscoveryProxy_Certificates, &gDiscoveryProxy_CertificateCount,	"hex string",	"Server certificate. Can be specified more than once.", true ),
+	CLI_OPTION_END()
+};
+
+//===========================================================================================================================
 //	Command Table
 //===========================================================================================================================
 
@@ -2701,6 +2829,7 @@
 static void	PIDToUUIDCmd( void );
 #endif
 static void	DNSProxyStateCmd( void );
+static void	CachedLocalRecordsCommand( void );
 static void	DaemonVersionCmd( void );
 
 static CLIOption		kGlobalOpts[] =
@@ -2728,39 +2857,42 @@
 	
 	// Uncommon commands.
 	
-	Command( "getnameinfo",			GetNameInfoCmd,			kGetNameInfoOpts,		"Calls getnameinfo() and prints results.", true ),
-	Command( "getAddrInfoStress",	GetAddrInfoStressCmd,	kGetAddrInfoStressOpts,	"Runs DNSServiceGetAddrInfo() stress testing.", true ),
-	Command( "DNSQuery",			DNSQueryCmd,			kDNSQueryOpts,			"Crafts and sends a DNS query.", true ),
+	Command( "getnameinfo",				GetNameInfoCmd,				kGetNameInfoOpts,		"Calls getnameinfo() and prints results.", true ),
+	Command( "getAddrInfoStress",		GetAddrInfoStressCmd,		kGetAddrInfoStressOpts,	"Runs DNSServiceGetAddrInfo() stress testing.", true ),
+	Command( "DNSQuery",				DNSQueryCmd,				kDNSQueryOpts,			"Crafts and sends a DNS query.", true ),
 #if( DNSSDUTIL_INCLUDE_DNSCRYPT )
-	Command( "DNSCrypt",			DNSCryptCmd,			kDNSCryptOpts,			"Crafts and sends a DNSCrypt query.", true ),
+	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( "mdnscollider",		MDNSColliderCmd,		kMDNSColliderOpts,		"Creates record name collision scenarios.", 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 ),
 #if( TARGET_OS_DARWIN )
-	Command( "pid2uuid",			PIDToUUIDCmd,			kPIDToUUIDOpts,			"Prints the UUID of a process.", true ),
+	Command( "pid2uuid",				PIDToUUIDCmd,				kPIDToUUIDOpts,			"Prints the UUID of a process.", true ),
 #endif
-	Command( "server",				DNSServerCommand,		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,				"Simple Service Discovery Protocol (SSDP).", true ),
+	Command( "server",					DNSServerCommand,			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,				"Simple Service Discovery Protocol (SSDP).", true ),
 #if( TARGET_OS_DARWIN )
-	Command( "legacy",				NULL,					kLegacyOpts,			"Legacy DNS API.", true ),
-	Command( "dnsconfig",			NULL,					kDNSConfigOpts,			"Add/remove a supplemental resolver entry to/from the system's DNS configuration.", true ),
-	Command( "xpcsend",				XPCSendCommand,			kXPCSendOpts,			"Sends a message to an XPC service.", true ),
+	Command( "legacy",					NULL,						kLegacyOpts,			"Legacy DNS API.", true ),
+	Command( "dnsconfig",				NULL,						kDNSConfigOpts,			"Add/remove a supplemental resolver entry to/from the system's DNS configuration.", true ),
+	Command( "xpcsend",					XPCSendCommand,				kXPCSendOpts,			"Sends a message to an XPC service.", true ),
 #endif
 #if( MDNSRESPONDER_PROJECT )
-	Command( "interfaceMonitor",	InterfaceMonitorCmd,	kInterfaceMonitorOpts,	"Instantiates an mdns_interface_monitor.", true ),
-	Command( "querier",				QuerierCommand,			kQuerierOpts,			"Sends a DNS query using mdns_querier.", true ),
-	Command( "dnsproxy",			DNSProxyCmd,			kDNSProxyOpts,			"Enables mDNSResponder's DNS proxy.", true ),
-	Command( "dnsproxy-state",		DNSProxyStateCmd,		NULL,					"Gets mDNSResponder's DNS proxy state dump.", true ),
-	Command( "getaddrinfo-new",		GetAddrInfoNewCommand,	kGetAddrInfoNewOpts,	"Uses dnssd_getaddrinfo to resolve a hostname to IP addresses.", false ),
-	Command( "tcpinfo",				TCPInfoCommand,			kTCPInfoOpts,			"Uses mdns_tcpinfo_* to get TCP info.", true ),
+	Command( "interfaceMonitor",		InterfaceMonitorCmd,		kInterfaceMonitorOpts,	"Instantiates an mdns_interface_monitor.", true ),
+	Command( "querier",					QuerierCommand,				kQuerierOpts,			"Sends a DNS query using mdns_querier.", true ),
+	Command( "dnsproxy",				DNSProxyCmd,				kDNSProxyOpts,			"Enables mDNSResponder's DNS proxy.", true ),
+	Command( "dnsproxy-state",			DNSProxyStateCmd,			NULL,					"Gets mDNSResponder's DNS proxy state dump.", true ),
+	Command( "getaddrinfo-new",			GetAddrInfoNewCommand,		kGetAddrInfoNewOpts,	"Uses dnssd_getaddrinfo to resolve a hostname to IP addresses.", false ),
+	Command( "tcpinfo",					TCPInfoCommand,				kTCPInfoOpts,			"Uses mdns_tcpinfo_* to get TCP info.", true ),
 #endif
-	Command( "pf",					NULL,					kPFOpts,				"Packet filter commands.", true ),
-	Command( "ipv4fwd",				NULL,					kIPv4FwdOpts,			"IPv4 forwarding commands.", true ),
-	Command( "ipv6fwd",				NULL,					kIPv6FwdOpts,			"IPv6 forwarding commands.", true ),
-	Command( "print",				PrintCommand,			kPrintOpts,				"Reads a DNS message in wire format and writes it to stdout in a human-readable form.", true ),
-	Command( "daemonVersion",		DaemonVersionCmd,		NULL,					"Prints the version of the DNS-SD daemon.", true ),
+	Command( "pf",						NULL,						kPFOpts,				"Packet filter commands.", true ),
+	Command( "ipv4fwd",					NULL,						kIPv4FwdOpts,			"IPv4 forwarding commands.", true ),
+	Command( "ipv6fwd",					NULL,						kIPv6FwdOpts,			"IPv6 forwarding commands.", true ),
+	Command( "print",					PrintCommand,				kPrintOpts,				"Reads a DNS message in wire format and writes it to stdout in a human-readable form.", true ),
+	Command( "wifi",					NULL,						kWiFiOpts,				"Wi-Fi commands.", true ),
+	Command( "discovery-proxy",			DiscoveryProxyCommand,		kDiscoveryProxyOpts,	"Enables mDNSResponder's discovery proxy.", true ),
+	Command( "cached-local-records",	CachedLocalRecordsCommand,	NULL,					"Uses mrc_cached_local_records_inquiry to inquire about cached .local records.", true ),
+	Command( "daemonVersion",			DaemonVersionCmd,			NULL,					"Prints the version of the DNS-SD daemon.", true ),
 	
 	CLI_COMMAND_HELP(),
 	CLI_OPTION_END()
@@ -3179,6 +3311,8 @@
 		size_t			inRDataLen );
 static CFTypeID	MDNSColliderGetTypeID( void );
 
+#define MDNSColliderForget( X )		ForgetCustomEx( X, MDNSColliderStop, CFRelease )
+
 //===========================================================================================================================
 //	ServiceBrowser
 //===========================================================================================================================
@@ -3700,6 +3834,7 @@
 			if( browseOnly )
 			{
 				signedResult = mdns_signed_browse_result_create_from_data( dataPtr, dataLen, &err );
+				bc_ulog( kLogLevelTrace, "Signed browse result -- %@", signedResult );
 				if( signedResult )
 				{
 					uint8_t		instanceName[ kDomainNameLengthMax ];
@@ -3873,6 +4008,7 @@
 			mdns_signed_browse_result_t		signedResult;
 			
 			signedResult = mdns_signed_browse_result_create_from_data( dataPtr, dataLen, &err );
+			bc_ulog( kLogLevelTrace, "Signed browse result -- %@", signedResult );
 			if( signedResult )
 			{
 				if( mdns_signed_browse_result_covers_txt_rdata( signedResult, inRDataPtr, inRDataLen ) )
@@ -3942,6 +4078,7 @@
 			mdns_signed_resolve_result_t		signedResult;
 			
 			signedResult = mdns_signed_resolve_result_create_from_data( dataPtr, dataLen, &err );
+			bc_ulog( kLogLevelTrace, "Signed resolve result -- %@", signedResult );
 			if( signedResult )
 			{
 				if( mdns_signed_resolve_result_covers_txt_rdata( signedResult, inTXTPtr, inTXTLen ) )
@@ -4239,6 +4376,7 @@
 	DNSServiceRef		mainRef;			// Main sdRef for shared connection.
 	DNSServiceRef		opRef;				// sdRef for the DNSServiceQueryRecord operation.
 	const char *		recordName;			// Resource record name argument for DNSServiceQueryRecord().
+	uuid_t *			resolverOverride;	// UUID of libnetwork DNS resolver configuration to use for resolver override.
 	DNSServiceFlags		flags;				// Flags argument for DNSServiceQueryRecord().
 	uint32_t			ifIndex;			// Interface index argument for DNSServiceQueryRecord().
 	int					timeLimitSecs;		// Time limit for the DNSServiceQueryRecord() operation in seconds.
@@ -4333,6 +4471,23 @@
 	err = RecordTypeFromArgString( gQueryRecord_Type, &context->recordType );
 	require_noerr( err, exit );
 	
+	// Get resolver override UUID.
+	
+	if( gQueryRecord_ResolverOverride )
+	{
+		uuid_t		uuid;
+		
+		err = uuid_parse( gQueryRecord_ResolverOverride, uuid );
+		if( err )
+		{
+			FPrintF( stderr, "Invalid resolver UUID: %s\n", gQueryRecord_ResolverOverride );
+			err = kParamErr;
+			goto exit;
+		}
+		context->resolverOverride = (uuid_t *) _memdup( uuid, sizeof( uuid ) );
+		require_action( context->resolverOverride, exit, err = kNoMemoryErr );
+	}
+	
 	// Set remaining parameters.
 	
 	context->recordName			= gQueryRecord_Name;
@@ -4348,7 +4503,7 @@
 	
 	// Start operation.
 	
-	if( context->useAAAAFallback || context->useFailover )
+	if( context->useAAAAFallback || context->useFailover || context->resolverOverride )
 	{
 		attr = DNSServiceAttributeCreate();
 		require_action( attr, exit, err = kNoResourcesErr );
@@ -4362,6 +4517,20 @@
 			err = DNSServiceAttrSetFailoverPolicy( attr, kDNSServiceFailoverPolicyAllow );
 			require_noerr( err, exit );
 		}
+		if( context->resolverOverride )
+		{
+			if( __builtin_available( macOS 15.0, iOS 18.0, tvOS 18.0, watchOS 11.0, * ) )
+			{
+				err = DNSServiceAttributeSetResolverOverride( attr, *context->resolverOverride );
+				require_noerr( err, exit );
+			}
+			else
+			{
+				FPrintF( stderr, "DNSServiceAttributeSetResolverOverride() is not available on this OS build." );
+				err = kUnsupportedErr;
+				goto exit;
+			}
+		}
 	}
 	sdRef = useMainConnection ? context->mainRef : kBadDNSServiceRef;
 	if( attr )
@@ -4410,6 +4579,7 @@
 {
 	DNSServiceForget( &inContext->opRef );
 	DNSServiceForget( &inContext->mainRef );
+	ForgetMem( &inContext->resolverOverride );
 	free( inContext );
 }
 
@@ -4423,17 +4593,24 @@
 	char			ifName[ kInterfaceNameBufLen ];
 	
 	InterfaceIndexToName( inContext->ifIndex, ifName );
-	
-	FPrintF( stdout, "Flags:          %#{flags}\n",	inContext->flags, kDNSServiceFlagsDescriptors );
-	FPrintF( stdout, "Interface:      %d (%s)\n",	(int32_t) inContext->ifIndex, ifName );
-	FPrintF( stdout, "Name:           %s\n",		inContext->recordName );
-	FPrintF( stdout, "Type:           %s (%u)\n",	RecordTypeToString( inContext->recordType ), inContext->recordType );
-	FPrintF( stdout, "AAAA Fallback:  %s\n",		YesNoStr( inContext->useAAAAFallback ) );
-	FPrintF( stdout, "Mode:           %s\n",		inContext->oneShotMode ? "one-shot" : "continuous" );
-	FPrintF( stdout, "Time limit:     " );
+	const char *resolverOverrideStr = "n/a";
+	uuid_string_t resolverOverrideUUIDStr;
+	if( inContext->resolverOverride )
+	{
+		uuid_unparse_upper( *inContext->resolverOverride, resolverOverrideUUIDStr );
+		resolverOverrideStr = resolverOverrideUUIDStr;
+	}
+	FPrintF( stdout, "Flags:              %#{flags}\n",	inContext->flags, kDNSServiceFlagsDescriptors );
+	FPrintF( stdout, "Interface:          %d (%s)\n",	(int32_t) inContext->ifIndex, ifName );
+	FPrintF( stdout, "Name:               %s\n",		inContext->recordName );
+	FPrintF( stdout, "Type:               %s (%u)\n",	RecordTypeToString( inContext->recordType ), inContext->recordType );
+	FPrintF( stdout, "AAAA Fallback:      %s\n",		YesNoStr( inContext->useAAAAFallback ) );
+	FPrintF( stdout, "Resolver Override:  %s\n",		resolverOverrideStr );
+	FPrintF( stdout, "Mode:               %s\n",		inContext->oneShotMode ? "one-shot" : "continuous" );
+	FPrintF( stdout, "Time limit:         " );
 	if( timeLimitSecs > 0 )	FPrintF( stdout, "%d second%?c\n", timeLimitSecs, timeLimitSecs != 1, 's' );
 	else					FPrintF( stdout, "∞\n" );
-	FPrintF( stdout, "Start time:     %{du:time}\n", NULL );
+	FPrintF( stdout, "Start time:         %{du:time}\n", NULL );
 	FPrintF( stdout, "---\n" );
 	
 }
@@ -4560,8 +4737,10 @@
 	uint32_t				ifIndex;			// Interface index argument for DNSServiceRegister().
 	int						lifetimeMs;			// Lifetime of the record registration in milliseconds.
 	uint32_t				timestamp;			// Timestamp in seconds since epoch time to indicate when the service is registered.
+	uint32_t				hostKeyHash;		// Host key hash value.
 	uint16_t				port;				// Service instance's port number.
 	Boolean					setTimestamp;		// True if the timestamp attribute needs to be set.
+	Boolean					setHostKeyHash;		// True if the host key hash attribute needs to be set.
 	Boolean					printedHeader;		// True if results header was printed.
 	Boolean					didRegister;		// True if service was registered.
 }	RegisterContext;
@@ -4643,7 +4822,13 @@
 
 		context->setTimestamp = true;
 	}
-	
+	if( gRegister_HostKeyHash )
+	{
+		err = _UInt32FromArgString( gRegister_HostKeyHash, "hostKeyHash", &context->hostKeyHash );
+		require_noerr_quiet( err, exit );
+
+		context->setHostKeyHash = true;
+	}
 	if( gAddRecord_TypesCount > 0 )
 	{
 		size_t		i;
@@ -4715,13 +4900,30 @@
 	
 	// Start operation.
 	
-	if( context->setTimestamp )
+	if( context->setTimestamp || context->setHostKeyHash )
 	{
 		attr = DNSServiceAttributeCreate();
 		require_action( attr, exit, err = kNoResourcesErr );
 		
-		err = DNSServiceAttributeSetTimestamp( attr, context->timestamp );
-		require_noerr( err, exit );
+		if( context->setTimestamp )
+		{
+			err = DNSServiceAttributeSetTimestamp( attr, context->timestamp );
+			require_noerr( err, exit );
+		}
+		if( context->setHostKeyHash )
+		{
+			if( __builtin_available( macOS 15.0, iOS 18.0, tvOS 18.0, watchOS 11.0, * ) )
+			{
+				err = DNSServiceAttributeSetHostKeyHash( attr, context->hostKeyHash );
+				require_noerr( err, exit );
+			}
+			else
+			{
+				FPrintF( stderr, "DNSServiceAttributeSetHostKeyHash() is not available on this OS build." );
+				err = kUnsupportedErr;
+				goto exit;
+			}
+		}
 	}
 	if( attr )
 	{
@@ -4763,25 +4965,29 @@
 	
 	InterfaceIndexToName( inContext->ifIndex, ifName );
 	
-	FPrintF( stdout, "Flags:      %#{flags}\n",	inContext->flags, kDNSServiceFlagsDescriptors );
-	FPrintF( stdout, "Interface:  %d (%s)\n",	(int32_t) inContext->ifIndex, ifName );
-	FPrintF( stdout, "Name:       %s\n",		inContext->name ? inContext->name : "<NULL>" );
-	FPrintF( stdout, "Type:       %s\n",		inContext->type );
-	FPrintF( stdout, "Domain:     %s\n",		inContext->domain ? inContext->domain : "<NULL> (default domains)" );
-	FPrintF( stdout, "Port:       %u\n",		inContext->port );
-	FPrintF( stdout, "TXT data:   %#{txt}\n",	inContext->txtPtr, inContext->txtLen );
+	FPrintF( stdout, "Flags:          %#{flags}\n",	inContext->flags, kDNSServiceFlagsDescriptors );
+	FPrintF( stdout, "Interface:      %d (%s)\n",	(int32_t) inContext->ifIndex, ifName );
+	FPrintF( stdout, "Name:           %s\n",		inContext->name ? inContext->name : "<NULL>" );
+	FPrintF( stdout, "Type:           %s\n",		inContext->type );
+	FPrintF( stdout, "Domain:         %s\n",		inContext->domain ? inContext->domain : "<NULL> (default domains)" );
+	FPrintF( stdout, "Port:           %u\n",		inContext->port );
+	FPrintF( stdout, "TXT data:       %#{txt}\n",	inContext->txtPtr, inContext->txtLen );
 	if( inContext->setTimestamp )
 	{
 		const char *		dateTimeStr;
 		char				dateTimeBuf[ 32 ];
 		
-		FPrintF( stdout, "Timestamp:  %u", inContext->timestamp );
+		FPrintF( stdout, "Timestamp:      %u", inContext->timestamp );
 		dateTimeStr = _UnixTimeToDateAndTimeString( inContext->timestamp, dateTimeBuf, sizeof( dateTimeBuf ) );
 		if( dateTimeStr ) FPrintF( stdout, " (%s)", dateTimeStr );
 		FPrintF( stdout, "\n" );
 	}
-	if( inContext->lifetimeMs < 0 )	FPrintF( stdout, "Lifetime:   ∞ ms\n" );
-	else							FPrintF( stdout, "Lifetime:   %d ms\n", inContext->lifetimeMs );
+	if( inContext->setHostKeyHash )
+	{
+		FPrintF( stdout, "Host Key Hash:  0x%08X (%u)\n", inContext->hostKeyHash, inContext->hostKeyHash );
+	}
+	if( inContext->lifetimeMs < 0 )	FPrintF( stdout, "Lifetime:       ∞ ms\n" );
+	else							FPrintF( stdout, "Lifetime:       %d ms\n", inContext->lifetimeMs );
 	
 	printedRecords = false;
 	if( inContext->extraRecordsCount > 0 )
@@ -4814,7 +5020,7 @@
 		}
 		printedRecords = true;
 	}
-	FPrintF( stdout, "%sStart time: %{du:time}\n", printedRecords ? "\n" : "", NULL );
+	FPrintF( stdout, "%sStart time:     %{du:time}\n", printedRecords ? "\n" : "", NULL );
 	FPrintF( stdout, "---\n" );
 }
 
@@ -4997,7 +5203,9 @@
 	uint32_t			updateTTL;		// TTL for updated record.
 	int					updateDelayMs;	// Post-registration record update delay in milliseconds.
 	uint32_t			timestamp;		// Timestamp in seconds since epoch time to indicate when the record is registered.
+	uint32_t			hostKeyHash;	// Host key hash value.
 	Boolean				setTimestamp;	// True if the timestamp attribute needs to be set.
+	Boolean				setHostKeyHash;	// True if the host key hash attribute needs to be set.
 	Boolean				didRegister;	// True if the record was registered.
 
 	
@@ -5072,6 +5280,13 @@
 
 		context->setTimestamp = true;
 	}
+	if( gRegisterRecord_HostKeyHash )
+	{
+		err = _UInt32FromArgString( gRegisterRecord_HostKeyHash, "hostKeyHash", &context->hostKeyHash );
+		require_noerr_quiet( err, exit );
+
+		context->setHostKeyHash = true;
+	}
 
 	// Get update data.
 	
@@ -5091,13 +5306,30 @@
 	// Start operation.
 	
 	// Only call DNSServiceAttributeSetTimestamp when the option is set.
-	if( context->setTimestamp )
+	if( context->setTimestamp || context->setHostKeyHash )
 	{
 		attr = DNSServiceAttributeCreate();
 		require_action( attr, exit, err = kNoResourcesErr );
 		
-		err = DNSServiceAttributeSetTimestamp( attr, context->timestamp );
-		require_noerr( err, exit );
+		if( context->setTimestamp )
+		{
+			err = DNSServiceAttributeSetTimestamp( attr, context->timestamp );
+			require_noerr( err, exit );
+		}
+		if( context->setHostKeyHash )
+		{
+			if( __builtin_available( macOS 15.0, iOS 18.0, tvOS 18.0, watchOS 11.0, * ) )
+			{
+				err = DNSServiceAttributeSetHostKeyHash( attr, context->hostKeyHash );
+				require_noerr( err, exit );
+			}
+			else
+			{
+				FPrintF( stderr, "DNSServiceAttributeSetHostKeyHash() is not available on this OS build." );
+				err = kUnsupportedErr;
+				goto exit;
+			}
+		}
 	}
 	if( attr )
 	{
@@ -5139,24 +5371,28 @@
 	
 	InterfaceIndexToName( inContext->ifIndex, ifName );
 	
-	FPrintF( stdout, "Flags:       %#{flags}\n",	inContext->flags, kDNSServiceFlagsDescriptors );
-	FPrintF( stdout, "Interface:   %d (%s)\n",		(int32_t) inContext->ifIndex, ifName );
-	FPrintF( stdout, "Name:        %s\n",			inContext->recordName );
-	FPrintF( stdout, "Type:        %s (%u)\n",		RecordTypeToString( inContext->recordType ), inContext->recordType );
-	FPrintF( stdout, "TTL:         %u\n",			inContext->ttl );
-	FPrintF( stdout, "Data:        %#H\n",			inContext->dataPtr, (int) inContext->dataLen, INT_MAX );
+	FPrintF( stdout, "Flags:          %#{flags}\n",	inContext->flags, kDNSServiceFlagsDescriptors );
+	FPrintF( stdout, "Interface:      %d (%s)\n",		(int32_t) inContext->ifIndex, ifName );
+	FPrintF( stdout, "Name:           %s\n",			inContext->recordName );
+	FPrintF( stdout, "Type:           %s (%u)\n",		RecordTypeToString( inContext->recordType ), inContext->recordType );
+	FPrintF( stdout, "TTL:            %u\n",			inContext->ttl );
+	FPrintF( stdout, "Data:           %#H\n",			inContext->dataPtr, (int) inContext->dataLen, INT_MAX );
 	if( inContext->setTimestamp )
 	{
 		const char *		dateTimeStr;
 		char				dateTimeBuf[ 32 ];
 		
-		FPrintF( stdout, "Timestamp:   %u", inContext->timestamp );
+		FPrintF( stdout, "Timestamp:      %u", inContext->timestamp );
 		dateTimeStr = _UnixTimeToDateAndTimeString( inContext->timestamp, dateTimeBuf, sizeof( dateTimeBuf ) );
 		if( dateTimeStr ) FPrintF( stdout, " (%s)", dateTimeStr );
 		FPrintF( stdout, "\n" );
 	}
+	if( inContext->setHostKeyHash )
+	{
+		FPrintF( stdout, "Host Key Hash:  0x%08X (%u)\n", inContext->hostKeyHash, inContext->hostKeyHash );
+	}
 	infinite = ( inContext->lifetimeMs < 0 ) ? true : false;
-	FPrintF( stdout, "Lifetime:    %?s%?d ms\n",	infinite, "∞", !infinite, inContext->lifetimeMs );
+	FPrintF( stdout, "Lifetime:       %?s%?d ms\n",	infinite, "∞", !infinite, inContext->lifetimeMs );
 	if( inContext->updateDataPtr )
 	{
 		FPrintF( stdout, "\nUpdate record:\n" );
@@ -5165,7 +5401,7 @@
 			inContext->updateTTL, inContext->updateTTL == 0, " (system will use a default value.)" );
 		FPrintF( stdout, "    RData:    %#H\n",		inContext->updateDataPtr, (int) inContext->updateDataLen, INT_MAX );
 	}
-	FPrintF( stdout, "Start time:  %{du:time}\n",	NULL );
+	FPrintF( stdout, "Start time:     %{du:time}\n", NULL );
 	FPrintF( stdout, "---\n" );
 }
 
@@ -6461,12 +6697,16 @@
 
 static void	_BrowseAllExit( void *inContext )
 {
-	BrowseAllContext * const		context		= (BrowseAllContext *) inContext;
+	BrowseAllContext * const		context					= (BrowseAllContext *) inContext;
 	SBRDomain *						domain;
 	SBRServiceType *				type;
 	SBRServiceInstance *			instance;
 	SBRIPAddress *					ipaddr;
 	char							textBuf[ 512 ];
+	size_t							serviceInstanceCount	= 0;
+	size_t							serviceResolveCount		= 0;
+	uint64_t						totalDiscoveryTimeUs	= 0;
+	uint64_t						totalResolveTimeUs		= 0;
 	
 	dispatch_source_forget( &context->connectionTimer );
 	
@@ -6489,6 +6729,8 @@
 				char * const		lim = &textBuf[ countof( textBuf ) ];
 				char				ifname[ IF_NAMESIZE + 1 ];
 				
+				++serviceInstanceCount;
+				totalDiscoveryTimeUs += instance->discoverTimeUs;
 				SNPrintF_Add( &dst, lim, "%s via ", instance->name );
 				if( instance->ifIndex == 0 )
 				{
@@ -6512,6 +6754,8 @@
 				
 				if( instance->hostname )
 				{
+					++serviceResolveCount;
+					totalResolveTimeUs += instance->resolveTimeUs;
 					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 );
@@ -6589,6 +6833,24 @@
 	}
 	
 	_BrowseAllContextFree( context );
+	
+	// Additional Stats
+	
+	FPrintF( stdout, "---\n" );
+	FPrintF( stdout, "Service instance count: %zu\n", serviceInstanceCount );
+	FPrintF( stdout, "Service resolve count:  %zu\n", serviceResolveCount );
+	if( serviceInstanceCount > 0 )
+	{
+		const uint64_t averageDiscoveryTimeUs = totalDiscoveryTimeUs / serviceInstanceCount;
+		FPrintF( stdout,
+			"Average discovery time: %llu.%03llu ms\n", averageDiscoveryTimeUs / 1000, averageDiscoveryTimeUs % 1000 );
+	}
+	if( serviceResolveCount > 0 )
+	{
+		const uint64_t averageResolveTimeUs = totalResolveTimeUs / serviceResolveCount;
+		FPrintF( stdout,
+			"Average resolve time:   %llu.%03llu ms\n", averageResolveTimeUs / 1000, averageResolveTimeUs % 1000 );
+	}
 	Exit( NULL );
 }
 
@@ -8196,44 +8458,62 @@
 
 typedef struct
 {
-	DNSServerRef				server;				// Reference to the DNS server.
-	dispatch_queue_t			queue;				// Serial queue for server.
-	sockaddr_ip *				addrArray;			// Server's addresses.
-	size_t						addrCount;			// Count of server's addresses.
-	dispatch_source_t			sourceSigInt;		// Dispatch source for SIGINT.
-	dispatch_source_t			sourceSigTerm;		// Dispatch source for SIGTERM.
-	const char *				domainOverride;		// If non-NULL, server is to use this domain instead of "d.test.".
-	dispatch_group_t			group;				// Dispatch group to signal when command is done.
-	dispatch_source_t			processMonitor;		// Process monitor source for process being followed, if any.
-	nw_resolver_config_t		secureDNSConfig;	// Resolver configuration for DNS over TLS (DoT).
-	mdns_network_policy_t		domainPolicy;		// Networking policy for matching domains to DoT service.
-	SecIdentityRef				secIdentity;		// Security identity associated with a self-signed certificate for DoT.
-	nw_listener_t				tlsListener;		// TLS listener.
-	DNSProtocol					protocol;			// DNS protocol to use, e.g., Do53, DoT, or DoH.
-	pid_t						followPID;			// PID of process being followed, if any. If it exits, we exit.
-	int32_t						refCount;			// Object's reference count.
-	OSStatus					error;				// Error encounted while running server.
-	uint16_t					portRequested;		// The port that was requested by the user.
-	uint16_t					portActual;			// DNS server's actual port number.
-	Boolean						loopbackOnly;		// True if the server should be bound to the loopback interface.
-	Boolean						addedResolver;		// True if a resolver entry was added to the system DNS settings.
-	Boolean						stopped;			// True if the command has stopped.
+	DNSServerRef						server;				// Reference to the DNS server.
+	dispatch_queue_t					queue;				// Serial queue for server.
+	sockaddr_ip *						addrArray;			// Server's addresses.
+	size_t								addrCount;			// Count of server's addresses.
+	dispatch_source_t					sourceSigInt;		// Dispatch source for SIGINT.
+	dispatch_source_t					sourceSigTerm;		// Dispatch source for SIGTERM.
+	const char *						domainOverride;		// If non-NULL, server is to use this domain instead of d.test.
+	dispatch_group_t					group;				// Dispatch group to signal when command is done.
+	dispatch_source_t					processMonitor;		// Process monitor source for process being followed, if any.
+	nw_resolver_config_t				secureDNSConfig;	// Resolver configuration for DNS over TLS (DoT).
+	mdns_network_policy_t				domainPolicy;		// Networking policy for matching domains to DoT service.
+	SecIdentityRef						secIdentity;		// Security identity associated with self-signed DoT certificate.
+	nw_listener_t						tlsListener;		// TLS listener.
+	mrc_dns_service_registration_t		registration;		// DNS service registration for Do53.
+	DNSProtocol							protocol;			// DNS protocol to use, e.g., Do53, DoT, or DoH.
+	pid_t								followPID;			// PID of process being followed, if any. If it exits, we exit.
+	int32_t								refCount;			// Object's reference count.
+	OSStatus							error;				// Error encounted while running server.
+	uint16_t							portRequested;		// The port that was requested by the user.
+	Boolean								loopbackOnly;		// True if the server should be bound to the loopback interface.
+	Boolean								addedResolver;		// True if a resolver entry was added to the system DNS settings.
+	Boolean								registerWithSC;		// True if Do53 is to be registered with SystemConfiguration.
+	Boolean								matchAllDomains;	// Include '.' as match domain if registering Do53 with SC. [1]
+	Boolean								stopped;			// True if the command has stopped.
 	
 }	DNSServerCmd;
 
+// Notes:
+// 1. If registering a Do53 service with SystemConfiguration, include '.' (root domain) as a low-priority match domain.
+//    This is useful if a test scenario where a default DNS service needs to be available for domain names for which the
+//    DNS server is not authoritative. Note that this means that the server may end up getting queries for domain names
+//    that it doesn't recognize.
+//
+//    For example, suppose that a test involves iterating search domains. Currently, a negative response from a server
+//    is required to iterate to the next search domain in the search domain list. If a test device happens to not be
+//    connected to any network, then it won't have a DHCP-assigned DNS service to act as a default DNS service that
+//    could provide a potentially negative response.
+//
+//    If the test DNS server includes '.' as a low-priority match domain, then it can act as a default DNS service of
+//    last resort. configd usually sets up a default DNS service from DHCP with '.' as its match domain with an order
+//    value of 0, which gives that DNS service the highest level of priority when comparing it to a DNS service that
+//    has '.' as a match domain, but with a greater order value (lower order value means higher priority).
+
 static DNSServerCmd *	_DNSServerCmdCreate( OSStatus *outError );
 static void				_DNSServerCmdRetain( DNSServerCmd *inCmd );
 static void				_DNSServerCmdRelease( DNSServerCmd *inCmd );
 static void				_DNSServerCmdStart( void *inCtx );
 static void				_DNSServerCmdStop( DNSServerCmd *inCmd, OSStatus inError );
 static OSStatus			_DNSServerCmdAddExtraLoopbackAddrs( sockaddr_ip *inAddrArray, size_t inAddrCount );
-static void				_DNSServerCmdServerStartHandler( uint16_t inActualPort, void *inCtx );
+static void				_DNSServerCmdServerStartHandler( uint16_t inDNSServerPort, void *inCtx );
 static void				_DNSServerCmdServerStopHandler( OSStatus inError, void *inCtx );
 static void				_DNSServerCmdSIGINTHandler( void *inCtx );
 static void				_DNSServerCmdSIGTERMHandler( void *inCtx );
 static void				_DNSServerCmdShutdown( DNSServerCmd *inCtx, int inSignal );
 static void				_DNSServerCmdFollowedProcessHandler( void *inCtx );
-static OSStatus			_DNSServerCmdModifySystemSettings( DNSServerCmd *inCmd );
+static OSStatus			_DNSServerCmdModifySystemSettings( DNSServerCmd *inCmd, uint16_t inListeningPort );
 static OSStatus			_DNSServerCmdUndoSystemSettings( DNSServerCmd *inCmd );
 static SecIdentityRef	_DNSServerCmdCreateSecIdentity( OSStatus *outError );
 static OSStatus			_DNSServerCmdSetUpCertificate( SecIdentityRef inIdentity );
@@ -8241,10 +8521,15 @@
 static nw_listener_t
 	_DNSServerCmdCreateTLSListener(
 		sec_identity_t	inIdentity,
-		uint16_t		inPort,
+		uint16_t		inDesiredPort,
 		Boolean			inUseHTTPS,
 		OSStatus *		outError );
-static OSStatus			_DNSServerCmdHandleNewTLSConnection( DNSServerCmd *inCmd, nw_connection_t inConnection );
+static OSStatus
+	_DNSServerCmdHandleNewTLSConnection(
+		DNSServerCmd *	inCmd,
+		nw_connection_t	inConnection,
+		uint16_t		inTLSListeningPort,
+		uint16_t		inDNSServerPort );
 
 ulog_define_ex( kDNSSDUtilIdentifier, DNSServer, kLogLevelInfo, kLogFlags_None, "DNSServer", NULL );
 #define ds_ulog( LEVEL, ... )		ulog( &log_category_from_name( DNSServer ), (LEVEL), __VA_ARGS__ )
@@ -8327,6 +8612,9 @@
 			goto exit;
 		}
 	}
+	cmd->registerWithSC  = gDNSServer_RegisterWithSC  ? true : false;
+	cmd->matchAllDomains = gDNSServer_MatchAllDomains ? true : false;
+	
 	// Set up IP addresses.
 	
 	if( listenOnV4 ) ++cmd->addrCount;
@@ -8540,19 +8828,14 @@
 //===========================================================================================================================
 
 static void
-_DNSServerCmdServerStartHandler( const uint16_t inActualPort, void * const inCtx )
+_DNSServerCmdServerStartHandler( const uint16_t inDNSServerPort, void * const inCtx )
 {
 	OSStatus					err;
 	DNSServerCmd * const		cmd			= (DNSServerCmd *) inCtx;
 	sec_identity_t				secIdentity	= NULL;
 	
-	cmd->portActual = inActualPort;
 	if( cmd->loopbackOnly )
 	{
-		err = _DNSServerCmdModifySystemSettings( cmd );
-		require_noerr_action( err, exit, ds_ulog( kLogLevelError,
-			"Failed to modify system settings: %#m\n", err ) );
-		
 		if( _DNSProtocolIsSecure( cmd->protocol ) )
 		{
 			Boolean		useHTTPS;
@@ -8584,10 +8867,18 @@
 					switch( inState )
 					{
 						case nw_listener_state_ready:
-							ds_ulog( kLogLevelInfo, "Listening for TLS connections on port %d\n",
-								nw_listener_get_port( cmd->tlsListener ) );
+						{
+							const uint16_t listeningPort = nw_listener_get_port( cmd->tlsListener );
+							ds_ulog( kLogLevelInfo, "Listening for TLS connections on port %u\n", listeningPort );
+							
+							const OSStatus settingsErr = _DNSServerCmdModifySystemSettings( cmd, listeningPort );
+							if( settingsErr )
+							{
+								ds_ulog( kLogLevelError, "Failed to modify system settings: %#m\n", settingsErr );
+								_DNSServerCmdStop( cmd, settingsErr );
+							}
 							break;
-						
+						}
 						case nw_listener_state_failed:
 						{
 							OSStatus		listenerErr;
@@ -8613,14 +8904,23 @@
 			nw_listener_set_new_connection_handler( cmd->tlsListener,
 			^( const nw_connection_t inConnection )
 			{
-				OSStatus		connectionErr;
-				
-				connectionErr = _DNSServerCmdHandleNewTLSConnection( cmd, inConnection );
-				if( connectionErr ) _DNSServerCmdStop( cmd, connectionErr );
+				if( cmd->tlsListener )
+				{
+					const uint16_t listeningPort = nw_listener_get_port( cmd->tlsListener );
+					const OSStatus connectionErr = _DNSServerCmdHandleNewTLSConnection( cmd, inConnection, listeningPort,
+						inDNSServerPort );
+					if( connectionErr ) _DNSServerCmdStop( cmd, connectionErr );
+				}
 			} );
 			nw_listener_set_queue( cmd->tlsListener, cmd->queue );
 			nw_listener_start( cmd->tlsListener );
 		}
+		else
+		{
+			err = _DNSServerCmdModifySystemSettings( cmd, inDNSServerPort );
+			require_noerr_action( err, exit, ds_ulog( kLogLevelError,
+				"Failed to modify system settings: %#m\n", err ) );
+		}
 	}
 	err = kNoErr;
 	
@@ -8712,21 +9012,21 @@
 }
 //===========================================================================================================================
 
-#define kDNSServerHostname				"dns.apple.test"
-#define kDNSServerDoHURLPath			"/dns-query"
-#define kDNSServerDoHURLTemplate		"https://" kDNSServerHostname kDNSServerDoHURLPath
-#define kDNSServerServiceID				CFSTR( "com.apple.dnssdutil.server" )
+#define kDNSServerHostname			"dns.apple.test"
+#define kDNSServerDoHURLPath		"/dns-query"
+#define kDNSServerServiceID			CFSTR( "com.apple.dnssdutil.server" )
 
-static OSStatus	_DNSServerCmdModifySystemSettings( DNSServerCmd * const inCmd )
+static OSStatus	_DNSServerCmdModifySystemSettings( DNSServerCmd * const inCmd, const uint16_t inListeningPort )
 {
-	OSStatus					err;
-	mdns_dns_configurator_t		configurator	= NULL;
-	nw_resolver_config_t		secureDNSConfig	= NULL;
-	nw_endpoint_t				endpoint		= NULL;
-	const char *				primaryDomainStr;
-	CFArrayRef					domains			= NULL;
-	size_t						i;
-	char						dnssecDomainStr[ kDNSServiceMaxDomainName ];
+	OSStatus						err;
+	mdns_dns_configurator_t			configurator	= NULL;
+	mdns_dns_service_definition_t	definition		= NULL;
+	nw_resolver_config_t			secureDNSConfig	= NULL;
+	nw_endpoint_t					endpoint		= NULL;
+	const char *					primaryDomainStr;
+	CFArrayRef						domains			= NULL;
+	size_t							i;
+	char							dnssecDomainStr[ kDNSServiceMaxDomainName ];
 	
 	require_action_quiet( inCmd->addrCount > 0, exit, err = kCountErr );
 	
@@ -8734,28 +9034,29 @@
 	require_noerr( err, exit );
 	
 	primaryDomainStr = inCmd->domainOverride ? inCmd->domainOverride : "d.test.";
+	const char * const domainStrings[] =
+	{
+		primaryDomainStr,
+		dnssecDomainStr,
+		kDNSServerReverseIPv4DomainStr,
+		kDNSServerReverseIPv6DomainStr
+	};
 	switch( inCmd->protocol )
 	{
 		case kDNSProtocol_DoT:
 		case kDNSProtocol_DoH:
 		{
-			bool					ok;
-			const char * const		domainStrings[] =
-			{
-				primaryDomainStr,
-				dnssecDomainStr,
-				kDNSServerReverseIPv4DomainStr,
-				kDNSServerReverseIPv6DomainStr
-			};
-			
 			// Try to clean up a stale Do53 resolver entry for a previous server from the system's DNS configuration in case
 			// one happens to still be present. Such an entry may interfere with the system settings for the DoT server if
 			// they're for the same domain, i.e., queries may be incorrectly sent to the Do53 server which no longer exists.
 			
+			Boolean usingDefaultPort;
 			mdns_dns_configurator_deregister_configuration( kDNSServerServiceID, CFSTR( kDNSSDUtilIdentifier ) );
 			if( inCmd->protocol == kDNSProtocol_DoT )
 			{
-				endpoint = nw_endpoint_create_host_with_numeric_port( kDNSServerHostname, 0 );
+				usingDefaultPort = ( inListeningPort == kDNSPort_DoT );
+				const uint16_t endpointPort = usingDefaultPort ? 0 : inListeningPort;
+				endpoint = nw_endpoint_create_host_with_numeric_port( kDNSServerHostname, endpointPort );
 				require_action( endpoint, exit, err = kUnknownErr );
 				
 				secureDNSConfig = nw_resolver_config_create_tls( endpoint );
@@ -8764,7 +9065,20 @@
 			}
 			else
 			{
-				endpoint = nw_endpoint_create_url( kDNSServerDoHURLTemplate );
+				char *templateURL = NULL;
+				usingDefaultPort = ( inListeningPort == kDNSPort_DoH );
+				if( usingDefaultPort )
+				{
+					ASPrintF( &templateURL, "https://%s%s", kDNSServerHostname, kDNSServerDoHURLPath );
+					require_action( templateURL, exit, err = kNoMemoryErr );
+				}
+				else
+				{
+					ASPrintF( &templateURL, "https://%s:%u%s", kDNSServerHostname, inListeningPort, kDNSServerDoHURLPath );
+					require_action( templateURL, exit, err = kNoMemoryErr );
+				}
+				endpoint = nw_endpoint_create_url( templateURL );
+				ForgetMem( &templateURL );
 				require_action( endpoint, exit, err = kUnknownErr );
 				
 				secureDNSConfig = nw_resolver_config_create_https( endpoint );
@@ -8797,16 +9111,17 @@
 			}
 			for( i = 0; i < inCmd->addrCount; ++i )
 			{
-				const sockaddr_ip * const		sip = &inCmd->addrArray[ i ];
+				sockaddr_ip serverAddr;
+				SockAddrCopy( &inCmd->addrArray[ i ], &serverAddr );
+				SockAddrSetPort( &serverAddr.sa, usingDefaultPort ? 0 : inListeningPort );
+				char serverAddrStr[ kSockAddrStringMaxSize ];
+				err = SockAddrToString( &serverAddr.sa, kSockAddrStringFlagsNone, serverAddrStr );
+				require_noerr( err, exit );
 				
-				endpoint = nw_endpoint_create_address( &sip->sa );
-				require( endpoint, exit );
-				
-				nw_resolver_config_add_server_address( secureDNSConfig, endpoint );
-				nw_forget( &endpoint );
+				nw_resolver_config_add_name_server( secureDNSConfig, serverAddrStr );
 			}
 			nw_resolver_config_set_interface_name( secureDNSConfig, "lo0" );
-			ok = nw_resolver_config_publish( secureDNSConfig );
+			const bool ok = nw_resolver_config_publish( secureDNSConfig );
 			require_action( ok, exit, err = kUnknownErr );
 			
 			if( domains )
@@ -8824,42 +9139,127 @@
 		}
 		case kDNSProtocol_Do53:
 		{
-			configurator = mdns_dns_configurator_create_with_cfstring_id( kDNSServerServiceID, &err );
-			require_noerr( err, exit );
-			
-			err = mdns_dns_configurator_add_domain( configurator, primaryDomainStr, 0 );
-			require_noerr( err, exit );
-			
-			err = mdns_dns_configurator_add_domain( configurator, dnssecDomainStr, 0 );
-			require_noerr( err, exit );
-			
-			err = mdns_dns_configurator_add_domain( configurator, kDNSServerReverseIPv4DomainStr, 0 );
-			require_noerr( err, exit );
-			
-			err = mdns_dns_configurator_add_domain( configurator, kDNSServerReverseIPv6DomainStr, 0 );
-			require_noerr( err, exit );
-			
-			for( i = 0; i < inCmd->addrCount; ++i )
+			if( inCmd->registerWithSC )
 			{
-				const sockaddr_ip * const		sip = &inCmd->addrArray[ i ];
-				char							addrStr[ kSockAddrStringMaxSize ];
-				
-				err = SockAddrToString( &sip->sa, kSockAddrStringFlagsNoPort, addrStr );
+				configurator = mdns_dns_configurator_create_with_cfstring_id( kDNSServerServiceID, &err );
 				require_noerr( err, exit );
 				
-				err = mdns_dns_configurator_add_server_address_string( configurator, addrStr );
+				err = mdns_dns_configurator_add_domain( configurator, primaryDomainStr, 0 );
 				require_noerr( err, exit );
+				
+				err = mdns_dns_configurator_add_domain( configurator, dnssecDomainStr, 0 );
+				require_noerr( err, exit );
+				
+				err = mdns_dns_configurator_add_domain( configurator, kDNSServerReverseIPv4DomainStr, 0 );
+				require_noerr( err, exit );
+				
+				err = mdns_dns_configurator_add_domain( configurator, kDNSServerReverseIPv6DomainStr, 0 );
+				require_noerr( err, exit );
+				
+				if( inCmd->matchAllDomains )
+				{
+					// Use the highest possible order value to make the root match domain as low-priority as possible.
+					
+					err = mdns_dns_configurator_add_domain( configurator, ".", UINT32_MAX );
+					require_noerr( err, exit );
+				}
+				for( i = 0; i < inCmd->addrCount; ++i )
+				{
+					const sockaddr_ip * const		sip = &inCmd->addrArray[ i ];
+					char							addrStr[ kSockAddrStringMaxSize ];
+					
+					err = SockAddrToString( &sip->sa, kSockAddrStringFlagsNoPort, addrStr );
+					require_noerr( err, exit );
+					
+					err = mdns_dns_configurator_add_server_address_string( configurator, addrStr );
+					require_noerr( err, exit );
+				}
+				err = mdns_dns_configurator_set_port( configurator, inListeningPort );
+				require_noerr( err, exit );
+				
+				err = mdns_dns_configurator_set_interface( configurator, "lo0" );
+				require_noerr( err, exit );
+				
+				err = mdns_dns_configurator_register( configurator, CFSTR( kDNSSDUtilIdentifier ) );
+				require_noerr( err, exit );
+				
+				inCmd->addedResolver = true;
 			}
-			err = mdns_dns_configurator_set_port( configurator, inCmd->portActual );
-			require_noerr( err, exit );
-			
-			err = mdns_dns_configurator_set_interface( configurator, "lo0" );
-			require_noerr( err, exit );
-			
-			err = mdns_dns_configurator_register( configurator, CFSTR( kDNSSDUtilIdentifier ) );
-			require_noerr( err, exit );
-			
-			inCmd->addedResolver = true;
+			else
+			{
+				definition = mdns_dns_service_definition_create();
+				require_action( definition, exit, err = kNoResourcesErr );
+				
+				for( i = 0; i < countof( domainStrings ); ++i )
+				{
+					const char * const domainStr = domainStrings[ i ];
+					mdns_domain_name_t domain = mdns_domain_name_create( domainStr, mdns_domain_name_create_opts_none,
+						&err );
+					require_noerr_action( err, exit, ds_ulog( kLogLevelError,
+						"error: Failed to create domain name for '%s': %#m\n", domainStr, err ) );
+					
+					mdns_dns_service_definition_add_domain( definition, domain );
+					mdns_forget( &domain );
+				}
+				for( i = 0; i < inCmd->addrCount; ++i )
+				{
+					sockaddr_ip sip = inCmd->addrArray[ i ];
+					SockAddrSetPort( &sip, inListeningPort );
+					char addrStr[ kSockAddrStringMaxSize ];
+					err = SockAddrToString( &sip.sa, kSockAddrStringFlagsNone, addrStr );
+					require_noerr( err, exit );
+					
+					mdns_address_t serverAddr = mdns_address_create_from_ip_address_string( addrStr );
+					require_action( serverAddr, exit, err = kNoResourcesErr; ds_ulog( kLogLevelError,
+						"error: Failed to create address for '%s'\n", addrStr ) );
+					
+					err = mdns_dns_service_definition_append_server_address( definition, serverAddr );
+					mdns_forget( &serverAddr );
+					require_noerr( err, exit );
+				}
+				const uint32_t ifIndex = if_nametoindex( "lo0" );
+				err = map_global_value_errno( ifIndex != 0, ifIndex );
+				require_noerr_action_quiet( err, exit, ds_ulog( kLogLevelError,
+					"Failed to get interface index for lo0: %#m", err ) );
+				
+				mdns_dns_service_definition_set_interface_index( definition, ifIndex, false );
+				inCmd->registration = mrc_dns_service_registration_create( definition );
+				mdns_forget( &definition );
+				require_action_quiet( inCmd->registration, exit, err = kNoResourcesErr );
+				
+				mrc_dns_service_registration_set_queue( inCmd->registration, inCmd->queue );
+				mrc_dns_service_registration_set_event_handler( inCmd->registration,
+				^( const mrc_dns_service_registration_event_t inEvent, const OSStatus inError )
+				{
+					switch( inEvent )
+					{
+						case mrc_dns_service_registration_event_started:
+							ds_ulog( kLogLevelInfo, "DNS service registration started\n" );
+							break;
+						
+						case mrc_dns_service_registration_event_interruption:
+							ds_ulog( kLogLevelInfo, "DNS service registration interrupted\n" );
+							break;
+						
+						case mrc_dns_service_registration_event_invalidation:
+							if( inError )
+							{
+								ds_ulog( kLogLevelError, "DNS service registration invalidated with error: %#m\n", inError );
+							}
+							else
+							{
+								ds_ulog( kLogLevelInfo, "DNS service registration gracefully invalidated\n" );
+							}
+							break;
+
+						case mrc_dns_service_registration_event_connection_error:
+							ds_ulog( kLogLevelFault, "DNS service registration invalid event with error: %#m\n",
+								inError );
+							break;
+					}
+				} );
+				mrc_dns_service_registration_activate( inCmd->registration );
+			}
 			break;
 		}
 		default:
@@ -8869,6 +9269,7 @@
 	
 exit:
 	mdns_forget( &configurator );
+	mdns_forget( &definition );
 	nw_forget( &secureDNSConfig );
 	nw_forget( &endpoint );
 	CFForget( &domains );
@@ -8898,6 +9299,7 @@
 		
 		inCmd->addedResolver = false;
 	}
+	mrc_dns_service_registration_forget( &inCmd->registration );
 	err = kNoErr;
 	
 exit:
@@ -9065,7 +9467,7 @@
 static nw_listener_t
 	_DNSServerCmdCreateTLSListener(
 		const sec_identity_t	inIdentity,
-		const uint16_t			inPort,
+		const uint16_t			inDesiredPort,
 		const Boolean			inUseHTTPS,
 		OSStatus * const		outError )
 {
@@ -9076,7 +9478,6 @@
 	nw_interface_t				interface	= NULL;
 	nw_listener_t				listener	= NULL;
 	__block bool				tlsWasConfigured;
-	char						portStr[ 16 ];
 	
 	tlsWasConfigured = false;
 	const nw_parameters_configure_protocol_block_t configureTLS =
@@ -9118,10 +9519,18 @@
 	nw_parameters_require_interface( params, interface );
 	nw_parameters_set_server_mode( params, true );
 	nw_parameters_set_reuse_local_address( params, true );
-	SNPrintF( portStr, sizeof( portStr ), "%u", inPort );
-	listener = nw_listener_create_with_port( portStr, params );
-	require_action( listener, exit, err = kNoResourcesErr );
-	
+	if( inDesiredPort > 0 )
+	{
+		char portStr[ 16 ];
+		SNPrintF( portStr, sizeof( portStr ), "%u", inDesiredPort );
+		listener = nw_listener_create_with_port( portStr, params );
+		require_action( listener, exit, err = kNoResourcesErr );
+	}
+	else
+	{
+		listener = nw_listener_create( params );
+		require_action( listener, exit, err = kNoResourcesErr );
+	}
 	err = kNoErr;
 	
 exit:
@@ -9135,7 +9544,12 @@
 
 //===========================================================================================================================
 
-static OSStatus	_DNSServerCmdHandleNewTLSConnection( DNSServerCmd * const inCmd, const nw_connection_t inConnection )
+static OSStatus
+	_DNSServerCmdHandleNewTLSConnection(
+		DNSServerCmd * const	inCmd,
+		const nw_connection_t	inConnection,
+		const uint16_t			inTLSListeningPort,
+		const uint16_t			inDNSServerPort )
 {
 	OSStatus					err;
 	nw_connection_t				clientConnection;
@@ -9159,19 +9573,11 @@
 	switch( localAddr->sa_family )
 	{
 		case AF_INET:
-		{
-			const struct sockaddr_in * const		sin = (const struct sockaddr_in *) localAddr;
-			
-			_SockAddrInitIPv4( &serverAddr.v4, ntohl( sin->sin_addr.s_addr ), inCmd->portActual );
-			break;
-		}
 		case AF_INET6:
-		{
-			const struct sockaddr_in6 * const		sin6 = (const struct sockaddr_in6 *) localAddr;
-			
-			_SockAddrInitIPv6( &serverAddr.v6, sin6->sin6_addr.s6_addr, sin6->sin6_scope_id, inCmd->portActual );
+			SockAddrCopy( localAddr, &serverAddr.sa );
+			SockAddrSetPort( &serverAddr.sa, inDNSServerPort );
 			break;
-		}
+		
 		default:
 			err = kAddressErr;
 			goto exit;
@@ -9187,7 +9593,7 @@
 		err = mdns_doh_relay_set_request_uri_path( relayDoH, kDNSServerDoHURLPath );
 		require_noerr( err, exit );
 		
-		err = mdns_doh_relay_set_host_and_port( relayDoH, kDNSServerHostname, inCmd->portRequested );
+		err = mdns_doh_relay_set_host_and_port( relayDoH, kDNSServerHostname, inTLSListeningPort );
 		require_noerr( err, exit );
 	}
 	else
@@ -15033,7 +15439,7 @@
 	GAITestCase *					caseList;			// List of test cases.
 	GAITestCase *					currentCase;		// Pointer to the current test case.
 	GAITestItem *					currentItem;		// Pointer to the current test item.
-	const char *					protocolStr;		// DNS protocol to use, e.g., Do53, DoT, or DoH.
+	DNSProtocol						protocol;			// DNS protocol to use, e.g., Do53, DoT, or DoH.
 	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.
 	unsigned int					callDelayMs;		// Amount of time to wait before calling DNSServiceGetAddrInfo().
@@ -15050,6 +15456,7 @@
 	GAITesterResultsHandler_f		resultsHandler;		// User's results handler.
 	void *							resultsContext;		// User's results handler context.
 	int								probeTryCount;		// Number of remaining probe queries.
+	uint16_t						serverPortDo53;		// Do53 server port. Only relevant if server protocol is Do53.
 	
 	// Variables for current test item.
 	
@@ -15065,7 +15472,7 @@
 CF_CLASS_DEFINE( GAITester );
 
 static void		_GAITesterStartNextTest( GAITesterRef inTester );
-static OSStatus	_GAITesterCreatePacketCapture( pcap_t **outPCap );
+static OSStatus	_GAITesterCreatePacketCapture( uint16_t inDNSServerPort, pcap_t **outPCap );
 static void		_GAITesterProbeTimerEventHandler( void *inContext );
 static void		_GAITesterTimeout( void *inContext );
 static void DNSSD_API
@@ -15119,10 +15526,8 @@
 	
 	CF_OBJECT_CREATE( GAITester, obj, err, exit );
 	
-	obj->protocolStr = _DNSProtocolToString( inProtocol );
-	require_action_quiet( obj->protocolStr, exit, err = kValueErr );
-	
 	ReplaceDispatchQueue( &obj->queue, inQueue );
+	obj->protocol			= inProtocol;
 	obj->callDelayMs		= inCallDelayMs;
 	obj->serverPID			= -1;
 	obj->serverDelayMs		= inServerDelayMs;
@@ -15177,15 +15582,42 @@
 	OSStatus				err;
 	GAITesterRef const		me = (GAITesterRef) inContext;
 	
+	const char * const protocolStr = _DNSProtocolToString( me->protocol );
+	require_action( protocolStr, exit, err = kValueErr );
+	
+	int serverPort = -1;
+	switch( me->protocol )
+	{
+		case kDNSProtocol_Do53:
+			// Use a specific port for the Do53 server so that it can be used by the PCAP to filter Do53 traffic.
+		#if( DNSSDUTIL_TEST_USE_ALTERNATE_SERVER_PORT_FOR_DO53 )
+			me->serverPortDo53 = kDNSPort_Do53Alt;
+		#else
+			me->serverPortDo53 = kDNSPort_Do53;
+		#endif
+			serverPort = me->serverPortDo53;
+			break;
+		
+		case kDNSProtocol_DoT:
+		#if( DNSSDUTIL_TEST_USE_ALTERNATE_SERVER_PORT_FOR_DOT )
+			serverPort = 0; // Use any ephemeral port.
+		#endif
+			break;
+		
+		case kDNSProtocol_DoH:
+			break;
+	}
 	err = _SpawnCommand( &me->serverPID, NULL, NULL,
-		"dnssdutil server --loopback --follow %lld --protocol %s%?s%?d%?s%?d%?s",
+		"dnssdutil server --loopback --follow %lld --protocol %s%?s%?d%?s%?d%?s%?s%?d",
 		(int64_t) getpid(),
-		me->protocolStr,
+		protocolStr,
 		me->serverDefaultTTL >= 0,	" --defaultTTL ",
 		me->serverDefaultTTL >= 0,	me->serverDefaultTTL,
 		me->serverDelayMs    >= 0,	" --responseDelay ",
 		me->serverDelayMs    >= 0,	me->serverDelayMs,
-		me->badUDPMode,				" --badUDPMode" );
+		me->badUDPMode,				" --badUDPMode",
+		serverPort >= 0,			" --port ",
+		serverPort >= 0,			serverPort );
 	require_noerr_quiet( err, exit );
 	
 	me->probeTryCount = 4;
@@ -15378,10 +15810,10 @@
 	
 	// Start a packet capture for Do53 traffic.
 	
-	if( strcasecmp( me->protocolStr, kDNSProtocolStr_Do53 ) == 0 )
+	if( me->protocol == kDNSProtocol_Do53 )
 	{
 		check( !me->pcap );
-		err = _GAITesterCreatePacketCapture( &me->pcap );
+		err = _GAITesterCreatePacketCapture( me->serverPortDo53, &me->pcap );
 		require_noerr( err, exit );
 	}
 	
@@ -15439,7 +15871,7 @@
 //	_GAITesterCreatePacketCapture
 //===========================================================================================================================
 
-static OSStatus	_GAITesterCreatePacketCapture( pcap_t **outPCap )
+static OSStatus	_GAITesterCreatePacketCapture( const uint16_t inDNSServerPort, pcap_t ** const outPCap )
 {
 	OSStatus				err;
 	pcap_t *				pcap;
@@ -15467,7 +15899,12 @@
 	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 );
+	char *programStr = NULL;
+	ASPrintF( &programStr, "udp port %u", inDNSServerPort );
+	require_action( programStr, exit, err = kNoMemoryErr );
+	
+	err = pcap_compile( pcap, &program, programStr, 1, PCAP_NETMASK_UNKNOWN );
+	ForgetMem( &programStr );
 	require_noerr_action_string( err, exit, err = kUnknownErr, pcap_geterr( pcap ) );
 	
 	err = pcap_setfilter( pcap, &program );
@@ -17199,10 +17636,13 @@
 	err = _SpawnCommand( &context->replierPID, NULL, NULL, "%s", context->replierCmd );
 	require_noerr( err, exit );
 	
-	// Spawn a test DNS server
+	// Spawn a test DNS server.
+	// Use the --registerSC option because this test depends on a GAI operation for a domain name that matches a *.local
+	// seach domain. SystemConfiguration will set up a search domain for each of the DNS service's match domains.
 	
 	ASPrintF( &context->serverCmd,
-		"dnssdutil server --loopback --follow %lld --port 0 --defaultTTL 300 --domain %s.local.",
+		DNSSDUTIL_TEST_DNS_SERVER_COMMAND_PREAMBLE " --loopback --registerSC --follow %lld --defaultTTL 300"
+		" --domain %s.local.",
 		(int64_t) getpid(), context->labelStr );
 	require_action_quiet( context->serverCmd, exit, err = kUnknownErr );
 	
@@ -18847,7 +19287,8 @@
 	// mDNSResponder's view of the address will be such that address with index value 1 is first, address with index
 	// value 2 is second, etc.
 	
-	ASPrintF( &test->serverCmd, "dnssdutil server --loopback --follow %lld --ipv6 --extraIPv6 3%s",
+	ASPrintF( &test->serverCmd,
+		DNSSDUTIL_TEST_DNS_SERVER_COMMAND_PREAMBLE " --loopback --follow %lld --ipv6 --extraIPv6 3%s%s",
 		(int64_t) getpid(), test->useRefused ? " --useRefused" : "" );
 	require_action_quiet( test->serverCmd, exit, err = kUnknownErr );
 	
@@ -20173,7 +20614,6 @@
 	DNSServiceRef				probeGAI;			// Probe GAI for DNS server.
 	char *						probeHostname;		// Probe hostname.
 	mrc_dns_proxy_t				dnsProxy;			// DNS proxy reference.
-	DNSXConnRef					dnsProxyLegacy;		// Legacy DNS proxy connection reference.
 	dispatch_source_t			timer;				// Timer to put time limit on queries.
 	mdns_resolver_t				resolver;			// Resolver to represent the DNS proxy as a DNS service.
 	CFMutableDictionaryRef		report;				// Test's report.
@@ -20206,7 +20646,6 @@
 	int							dns64PrefixBitLen;	// Current DNS64 prefix length (valid if > 0).
 	uint8_t						dns64Prefix[ 16 ];	// Current DNS64 prefix (valid if dns64PrefixBitLen > 0).
 	char						tag[ 6 + 1 ];		// Current subtest's random tag to uniquify QNAMEs.
-	Boolean						useLegacyDNSProxy;	// True if the legacy DNS proxy should be used.
 	Boolean						synthesizedAAAA;	// True if the current subtest expects DNS64 synthesized AAAA records.
 	Boolean						startedSubtests;	// True if the test has started running subtests.
 };
@@ -20232,7 +20671,6 @@
 	err = _DNSProxyTestCreate( &test );
 	require_noerr( err, exit );
 	
-	test->useLegacyDNSProxy = gDNSProxyTest_UseLegacyDNSProxy ? true : false;
 	err = _DNSProxyTestRun( test, &passed );
 	require_noerr( err, exit );
 	
@@ -20352,7 +20790,6 @@
 		check( !me->probeGAI );
 		check( !me->probeHostname );
 		check( !me->dnsProxy );
-		check( !me->dnsProxyLegacy );
 		check( !me->timer );
 		check( !me->resolver );
 		check( !me->modeResults );
@@ -20395,6 +20832,8 @@
 	err = map_global_value_errno( me->loopbackIndex != 0, me->loopbackIndex );
 	require_noerr_action_quiet( err, exit, dpt_ulog( kLogLevelError, "Failed to get interface index for lo0: %#m", err ) );
 	
+	// The test DNS server uses an ephemeral port because the DNS proxy will use port 53 for itself.
+	
 	serverCmd = NULL;
 	ASPrintF( &serverCmd, "dnssdutil server --loopback --follow %lld --port 0 --defaultTTL 300 --responseDelay 10",
 		(int64_t) getpid() );
@@ -20445,7 +20884,6 @@
 
 static void		_DNSProxyTestSubtestCleanup( DNSProxyTestRef inTest );
 
-#define _DNSXForget( X )			ForgetCustom( X, DNSXRefDeAlloc )
 #define _mrc_dns_proxy_forget( X )	ForgetCustomEx( X, mrc_dns_proxy_invalidate, mrc_release )
 
 static void	_DNSProxyTestStop( DNSProxyTestRef me, OSStatus inError )
@@ -20461,7 +20899,6 @@
 	DNSServiceForget( &me->probeGAI );
 	ForgetMem( &me->probeHostname );
 	_mrc_dns_proxy_forget( &me->dnsProxy );
-	_DNSXForget( &me->dnsProxyLegacy );
 	dispatch_source_forget( &me->timer );
 	mdns_resolver_forget( &me->resolver );
 	me->prefixResults		= NULL;
@@ -20547,7 +20984,6 @@
 						me->transportResults	= NULL;
 						dpt_ulog( kLogLevelInfo, "Disabling DNS proxy\n" );
 						_mrc_dns_proxy_forget( &me->dnsProxy );
-						_DNSXForget( &me->dnsProxyLegacy );
 						if( ++me->prefixParamIdx == countof( kDNSProxyTestParams_DNS64Prefixes ) )
 						{
 							me->prefixParamIdx	= 0;
@@ -20752,8 +21188,6 @@
 
 //===========================================================================================================================
 
-static void	_DNSProxyTestDNSProxyCallback( DNSXConnRef inDNSProxy, DNSXErrorType inError );
-
 static OSStatus	_DNSProxyTestPrepareDNSProxy( DNSProxyTestRef me )
 {
 	OSStatus			err;
@@ -20785,84 +21219,52 @@
 		default:
 			FatalErrorF( "Unhandled DNSProxyTestMode value %ld", (long) me->modeParam );
 	}
-	if( me->useLegacyDNSProxy )
+	mrc_dns_proxy_parameters_t params = mrc_dns_proxy_parameters_create( &err );
+	require_noerr( err, exit );
+	
+	mrc_dns_proxy_parameters_add_input_interface( params, me->loopbackIndex );
+	mrc_dns_proxy_parameters_set_output_interface( params, 0 );
+	if( dns64PrefixStr )
 	{
-		IfIndex		interfaces[ MaxInputIf ];
-		
-		memset( interfaces, 0, sizeof( interfaces ) );
-		interfaces[ 0 ] = me->loopbackIndex;
-		
-		check( !me->dnsProxyLegacy );
-		if( dns64PrefixStr )
-		{
-			DNSXProxyFlags		flags;
-			
-			dpt_ulog( kLogLevelInfo, "Enabling legacy DNS proxy with DNS64 prefix %.16a/%d\n",
-				me->dns64Prefix, me->dns64PrefixBitLen );
-			flags = forceAAAASynthesis ? kDNSXProxyFlagForceAAAASynthesis : kDNSXProxyFlagNull;
-			err = DNSXEnableProxy64( &me->dnsProxyLegacy, kDNSProxyEnable, interfaces, 0, me->dns64Prefix,
-				me->dns64PrefixBitLen, flags, me->queue, _DNSProxyTestDNSProxyCallback );
-			require_noerr_quiet( err, exit );
-		}
-		else
-		{
-			dpt_ulog( kLogLevelInfo, "Enabling legacy DNS proxy (without a DNS64 prefix)\n" );
-			err = DNSXEnableProxy( &me->dnsProxyLegacy, kDNSProxyEnable, interfaces, 0, me->queue,
-				_DNSProxyTestDNSProxyCallback );
-			require_noerr_quiet( err, exit );
-		}
+		mrc_dns_proxy_parameters_set_nat64_prefix( params, me->dns64Prefix, (size_t) me->dns64PrefixBitLen );
+		if( forceAAAASynthesis ) mrc_dns_proxy_parameters_set_force_aaaa_synthesis( params, true );
+		dpt_ulog( kLogLevelInfo, "Starting DNS proxy with DNS64 prefix %.16a/%d\n",
+			me->dns64Prefix, me->dns64PrefixBitLen );
 	}
 	else
 	{
-		mrc_dns_proxy_parameters_t		params;
-		
-		params = mrc_dns_proxy_parameters_create( &err );
-		require_noerr( err, exit );
-		
-		mrc_dns_proxy_parameters_add_input_interface( params, me->loopbackIndex );
-		mrc_dns_proxy_parameters_set_output_interface( params, 0 );
-		if( dns64PrefixStr )
-		{
-			mrc_dns_proxy_parameters_set_nat64_prefix( params, me->dns64Prefix, (size_t) me->dns64PrefixBitLen );
-			if( forceAAAASynthesis ) mrc_dns_proxy_parameters_set_force_aaaa_synthesis( params, true );
-			dpt_ulog( kLogLevelInfo, "Starting DNS proxy with DNS64 prefix %.16a/%d\n",
-				me->dns64Prefix, me->dns64PrefixBitLen );
-		}
-		else
-		{
-			dpt_ulog( kLogLevelInfo, "Starting DNS proxy (without a DNS64 prefix)\n" );
-		}
-		check( !me->dnsProxy );
-		me->dnsProxy = mrc_dns_proxy_create( params, &err );
-		mrc_forget( &params );
-		require_noerr_quiet( err, exit );
-		
-		mrc_dns_proxy_set_queue( me->dnsProxy, me->queue );
-		mrc_dns_proxy_set_event_handler( me->dnsProxy,
-		^( const mrc_dns_proxy_event_t inEvent, const OSStatus inError )
-		{
-			switch( inEvent )
-			{
-				case mrc_dns_proxy_event_started:
-					dpt_ulog( kLogLevelInfo, "DNS proxy was started\n" );
-					break;
-				
-				case mrc_dns_proxy_event_interruption:
-					dpt_ulog( kLogLevelInfo, "DNS proxy was interrupted\n" );
-					break;
-				
-				case mrc_dns_proxy_event_invalidation:
-					if( inError )	dpt_ulog( kLogLevelError, "DNS proxy invalidated with error: %#m\n", inError );
-					else			dpt_ulog( kLogLevelInfo, "DNS proxy invalidated\n" );
-					break;
-				
-				default:
-				case mrc_dns_proxy_event_none:
-					FatalErrorF( "Unhandled DNS proxy event value: %d", inEvent );
-			}
-		} );
-		mrc_dns_proxy_activate( me->dnsProxy );
+		dpt_ulog( kLogLevelInfo, "Starting DNS proxy (without a DNS64 prefix)\n" );
 	}
+	check( !me->dnsProxy );
+	me->dnsProxy = mrc_dns_proxy_create( params, &err );
+	mrc_forget( &params );
+	require_noerr_quiet( err, exit );
+	
+	mrc_dns_proxy_set_queue( me->dnsProxy, me->queue );
+	mrc_dns_proxy_set_event_handler( me->dnsProxy,
+	^( const mrc_dns_proxy_event_t inEvent, const OSStatus inError )
+	{
+		switch( inEvent )
+		{
+			case mrc_dns_proxy_event_started:
+				dpt_ulog( kLogLevelInfo, "DNS proxy was started\n" );
+				break;
+			
+			case mrc_dns_proxy_event_interruption:
+				dpt_ulog( kLogLevelInfo, "DNS proxy was interrupted\n" );
+				break;
+			
+			case mrc_dns_proxy_event_invalidation:
+				if( inError )	dpt_ulog( kLogLevelError, "DNS proxy invalidated with error: %#m\n", inError );
+				else			dpt_ulog( kLogLevelInfo, "DNS proxy invalidated\n" );
+				break;
+			
+			default:
+			case mrc_dns_proxy_event_none:
+				FatalErrorF( "Unhandled DNS proxy event value: %d", inEvent );
+		}
+	} );
+	mrc_dns_proxy_activate( me->dnsProxy );
 	err = CFPropertyListAppendFormatted( kCFAllocatorDefault, me->prefixResults,
 		"{"
 			"%kO=%s"	// dns64Prefix
@@ -20876,14 +21278,6 @@
 	return( err );
 }
 
-static void	_DNSProxyTestDNSProxyCallback( DNSXConnRef inDNSProxy, DNSXErrorType inError )
-{
-	Unused( inDNSProxy );
-	
-	if( !inError )	dpt_ulog( kLogLevelInfo, "DNS proxy callback: no error\n" );
-	else			dpt_ulog( kLogLevelError, "DNS proxy callback: error %#m\n", inError );
-}
-
 //===========================================================================================================================
 
 #define kDNSProxyTestDNSProxyAddrStr_IPv4		"127.0.0.1:53"
@@ -21967,7 +22361,8 @@
 	
 	serverCmd = NULL;
 	ASPrintF( &serverCmd,
-		"dnssdutil server --loopback --follow %lld --responseDelay 20 --ipv6 --extraIPv6 3", (int64_t) getpid() );
+		DNSSDUTIL_TEST_DNS_SERVER_COMMAND_PREAMBLE " --loopback --follow %lld --responseDelay 20 --ipv6 --extraIPv6 3",
+		(int64_t) getpid() );
 	require_action_quiet( serverCmd, exit, err = kNoMemoryErr );
 	
 	err = _SpawnCommand( &me->serverPID, "/dev/null", "/dev/null", "%s", serverCmd );
@@ -22615,6 +23010,7 @@
 	OSStatus					error;				// Current test error.
 	int							subtestCount;		// Number of subtests that have completed or are in progress.
 	int							subtestPassCount;	// Number of subtests that have passed so far.
+	uint16_t					serverPort;			// Port number used by DNS server.
 	Boolean						haveBadQueryCounts;	// True if the current subtest's query counts are incorrect.
 	Boolean						done;				// True if all subtests have completed.
 };
@@ -22766,7 +23162,13 @@
 	dqt_ulog( kLogLevelInfo, "Starting test\n" );
 	
 	serverCmd = NULL;
-	ASPrintF( &serverCmd, "dnssdutil server --loopback --follow %lld --responseDelay 10", (int64_t) getpid() );
+#if( DNSSDUTIL_TEST_USE_ALTERNATE_SERVER_PORT_FOR_DO53 )
+	me->serverPort = kDNSPort_Do53Alt;
+#else
+	me->serverPort = kDNSPort_Do53;
+#endif
+	ASPrintF( &serverCmd, "dnssdutil server --loopback --follow %lld --port %u --responseDelay 10",
+		(int64_t) getpid(), me->serverPort );
 	require_action_quiet( serverCmd, exit, err = kNoMemoryErr );
 	
 	err = _SpawnCommand( &me->serverPID, "/dev/null", "/dev/null", "%s", serverCmd );
@@ -23020,7 +23422,7 @@
 	} );
 	
 	check( !me->pcap );
-	err = _GAITesterCreatePacketCapture( &me->pcap );
+	err = _GAITesterCreatePacketCapture( me->serverPort, &me->pcap );
 	require_noerr( err, exit );
 	
 	check( !me->timer );
@@ -23610,7 +24012,8 @@
 	frt_ulog( kLogLevelInfo, "Starting test\n" );
 	
 	serverCmd = NULL;
-	ASPrintF( &serverCmd, "dnssdutil server --loopback --follow %lld --responseDelay 10", (int64_t) getpid() );
+	ASPrintF( &serverCmd, DNSSDUTIL_TEST_DNS_SERVER_COMMAND_PREAMBLE " --loopback --follow %lld --responseDelay 10",
+		(int64_t) getpid() );
 	require_action_quiet( serverCmd, exit, err = kNoMemoryErr );
 	
 	err = _SpawnCommand( &me->serverPID, "/dev/null", "/dev/null", "%s", serverCmd );
@@ -26388,10 +26791,10 @@
 	Unused( context );
 	pid_t pid = getpid();
 
-	OSStatus err = _SpawnCommand( &context->localServerPID, NULL, NULL, "dnssdutil server --loopback --follow %lld",
-		(int64_t) pid );
-	require_noerr_action( err, exit,
-		FPrintF( stderr, "dnssdutil server --loopback --follow %lld failed, error: %d\n", (int64_t) pid, err ) );
+	OSStatus err = _SpawnCommand( &context->localServerPID, NULL, NULL,
+		DNSSDUTIL_TEST_DNS_SERVER_COMMAND_PREAMBLE " --loopback --follow %lld", (int64_t) pid );
+	require_noerr_action( err, exit, FPrintF( stderr,
+		DNSSDUTIL_TEST_DNS_SERVER_COMMAND_PREAMBLE " --loopback --follow %lld failed, error: %d\n", (int64_t) pid, err ) );
 
 	// Wait long enough to allow the DNS server being setup.
 	sleep( 2 );
@@ -26794,6 +27197,3875 @@
 #endif // #if ( ENABLE_DNSSDUTIL_DNSSEC_TEST == 1 )
 
 //===========================================================================================================================
+//	OptimisticDNSTestCommand
+//===========================================================================================================================
+
+typedef struct OptimisticDNSTest *		OptimisticDNSTestRef;
+struct OptimisticDNSTest
+{
+	dispatch_queue_t			queue;			// Serial queue for test events.
+	dispatch_semaphore_t		doneSem;		// Semaphore to signal when the test is done.
+	dnssd_getaddrinfo_t			gai;			// For GAI operations.
+	dispatch_source_t			timer;			// Timer for enforcing time limits.
+	mdns_domain_name_t			domain;			// High-level domain for test's hostnames.
+	char *						hostname;		// Hostname used in current GAI operation.
+	CFPropertyListRef			savedAASDPref;	// The saved AlwaysAppendSearchDomains preference value. [1]
+	pid_t						serverPID;		// PID of spawned test DNS server.
+	int32_t						refCount;		// Test's reference count.
+	OSStatus					error;			// Test's error code.
+	sockaddr_ip					expectedAddr;	// The expected IP address from a GAI operation.
+	Boolean						modifiedDaemon;	// True if the test needs to undo mDNSResponder modifications. [2]
+	Boolean						fullTest;		// Run full test, even if mDNSResponder needs to be killed.
+	uint8_t						expectedHostname[ kDomainNameLengthMax ];
+};
+
+// Notes:
+// 1. The saved AlwaysAppendSearchDomains preference will be restored after the test is done testing with its own
+//    AlwaysAppendSearchDomains value.
+// 2. For example, the test might have set a preference and restarted mDNSResponder for the preference to take effect.
+//    Cleanup would consist of restoring the preference and restarting mDNSResponder again.
+
+static OptimisticDNSTestRef	_OptimisticDNSTestCreate( OSStatus *outError, Boolean inFullTest );
+static OSStatus				_OptimisticDNSTestRun( OptimisticDNSTestRef inTest );
+static void					_OptimisticDNSTestRelease( OptimisticDNSTestRef inTest );
+
+static void	OptimisticDNSTestCommand( void )
+{
+	OSStatus err;
+	OptimisticDNSTestRef test = NULL;
+	const Boolean fullTest = ( gOptimisticDNSTest_FullTest != 0 );
+	if( fullTest )
+	{
+		err = CheckRootUser();
+		require_noerr_quiet( err, exit );
+	}
+	test = _OptimisticDNSTestCreate( &err, fullTest );
+	require_noerr( err, exit );
+	
+	err = _OptimisticDNSTestRun( test );
+	require_noerr( err, exit );
+	
+exit:
+	if( test ) _OptimisticDNSTestRelease( test );
+    gExitCode = err ? 1 : 0;
+}
+
+//===========================================================================================================================
+
+static OptimisticDNSTestRef	_OptimisticDNSTestCreate( OSStatus * const outError, const Boolean inFullTest )
+{
+	OSStatus err;
+	OptimisticDNSTestRef test = NULL;
+	OptimisticDNSTestRef obj = (OptimisticDNSTestRef) calloc( 1, sizeof( *obj ) );
+	require_action( obj, exit, err = kNoMemoryErr );
+	
+	obj->refCount	= 1;
+	obj->error		= kInProgressErr;
+	obj->serverPID	= -1;
+	obj->fullTest	= inFullTest;
+	
+	obj->queue = dispatch_queue_create( "com.apple.dnssdutil.optimistic-dns-test", DISPATCH_QUEUE_SERIAL );
+	require_action( obj->queue, exit, err = kNoResourcesErr );
+	
+	obj->doneSem = dispatch_semaphore_create( 0 );
+	require_action( obj->doneSem, exit, err = kNoResourcesErr );
+	
+	test = obj;
+	obj = NULL;
+	err = kNoErr;
+	
+exit:
+	if( outError ) *outError = err;
+	if( obj ) _OptimisticDNSTestRelease( obj );
+	return( test );
+}
+
+//===========================================================================================================================
+
+static void	_OptimisticDNSTestRetain( const OptimisticDNSTestRef me )
+{
+	atomic_add_32( &me->refCount, 1 );
+}
+
+//===========================================================================================================================
+
+static void	_OptimisticDNSTestStop( const OptimisticDNSTestRef me, const OSStatus inError )
+{
+	me->error = inError;
+	if( !me->error )
+	{
+		FPrintF( stdout, "%{du:time} Test PASSED\n", NULL );
+	}
+	else
+	{
+		FPrintF( stdout, "%{du:time} Test FAILED: %#m\n", NULL, me->error );
+	}
+	dnssd_getaddrinfo_forget( &me->gai );
+	dispatch_source_forget( &me->timer );
+	mdns_forget( &me->domain );
+	ForgetMem( &me->hostname );
+	if( me->serverPID >= 0 )
+	{
+		OSStatus		killErr;
+		
+		killErr = kill( me->serverPID, SIGTERM );
+		killErr = map_global_noerr_errno( killErr );
+		check_noerr( killErr );
+		me->serverPID = -1;
+	}
+	if( me->modifiedDaemon )
+	{
+		const CFStringRef keyAASD = CFSTR( kMDNSResponderPrefStr_AlwaysAppendSearchDomains );
+		FPrintF( stdout, "\n%{du:time} Cleanup:\n", NULL );
+		FPrintF( stdout, "%{du:time} âš’ Restoring %@ preference to '%@'\n", NULL, keyAASD, me->savedAASDPref );
+		const CFStringRef appID = CFSTR( kMDNSResponderPrefAppIDStr );
+		CFPreferencesSetValue( keyAASD, me->savedAASDPref, appID, kCFPreferencesAnyUser, kCFPreferencesCurrentHost );
+		FPrintF( stdout, "%{du:time} ↻ Restarting mDNSResponder\n", NULL );
+		const OSStatus killErr = systemf( NULL, "killall -KILL mDNSResponder" );
+		if( killErr )
+		{
+			FPrintF( stdout, "%{du:time} x Error killing mDNSResponder: %#m\n", NULL, killErr );
+		}
+	}
+	CFForget( &me->savedAASDPref );
+	dispatch_semaphore_signal( me->doneSem );
+}
+
+//===========================================================================================================================
+
+static dnssd_getaddrinfo_t
+	_OptimisticDNSTestCreateGAI(
+		const OptimisticDNSTestRef	me,
+		const char * const			inHostname,
+		const DNSServiceFlags		inFlags )
+{
+	const dnssd_getaddrinfo_t gai = dnssd_getaddrinfo_create();
+	require_quiet( gai, exit );
+	
+	dnssd_getaddrinfo_set_hostname( gai, inHostname );
+	dnssd_getaddrinfo_set_flags( gai, inFlags );
+	dnssd_getaddrinfo_set_interface_index( gai, kDNSServiceInterfaceIndexAny );
+	dnssd_getaddrinfo_set_protocols( gai, kDNSServiceProtocol_IPv4 );
+	dnssd_getaddrinfo_set_queue( gai, me->queue );
+	dnssd_retain( gai );
+	_OptimisticDNSTestRetain( me );
+	dnssd_getaddrinfo_set_event_handler( gai,
+	^( const dnssd_event_t inEvent, const DNSServiceErrorType inGAIError )
+	{
+		switch( inEvent )
+		{
+			case dnssd_event_invalidated:
+				dnssd_release( gai );
+				_OptimisticDNSTestRelease( me );
+				break;
+			
+			case dnssd_event_error:
+				require_return( me->gai == gai );
+				
+				FPrintF( stdout, "dnssd_getaddrinfo error: %#m\n", inGAIError );
+				_OptimisticDNSTestStop( me, inGAIError );
+				break;
+			
+			case dnssd_event_remove_all:
+				break;
+		}
+	} );
+	
+exit:
+	return( gai );
+}
+
+//===========================================================================================================================
+
+static OSStatus	_OptimisticDNSTestStartSubtest7_2( const OptimisticDNSTestRef me )
+{
+	OSStatus err;
+	__block Boolean gotExpiredResult = false;
+	__block Boolean gotAddResult = false;
+	FPrintF( stdout,
+		"%{du:time} Subtest 7.2: Second multi-label PQDN GAI for hostname that has one CNAME record and that doesn't"
+		" depend on a search domain (CNAME record expired, AlwaysAppendSearchDomains enabled)\n",
+		NULL );
+	
+	// Start the GAI operation.
+	
+	check( me->hostname );
+	const unsigned int timeLimitSecs = 5;
+	FPrintF( stdout, "%{du:time} * Starting GAI for multi-label PQDN '%s' with %u second time limit\n",
+		NULL, me->hostname, timeLimitSecs );
+	const DNSServiceFlags flags = kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsAllowExpiredAnswers;
+	me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, flags );
+	require_action( me->gai, exit, err = kNoResourcesErr );
+	
+	const dnssd_getaddrinfo_t gai = me->gai;
+	dnssd_getaddrinfo_set_result_handler( me->gai,
+	^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount )
+	{
+		require_return( me->gai == gai );
+		
+		size_t unexpectedResultCount = 0;
+		for( size_t i = 0; i < inCount; ++i )
+		{
+			Boolean resultIsExpected = false;
+			const dnssd_getaddrinfo_result_t result = inResults[ i ];
+			const dnssd_getaddrinfo_result_type_t resultType = dnssd_getaddrinfo_result_get_type( result );
+			const char * const actualHostnameStr = dnssd_getaddrinfo_result_get_actual_hostname( result );
+			const bool fromCache = dnssd_getaddrinfo_result_is_from_cache( result );
+			if( SockAddrCompareAddr( dnssd_getaddrinfo_result_get_address( result ), &me->expectedAddr.sa ) == 0 )
+			{
+				// Since this is a GAI for a single-label instead of a FQDN, to make sure that appropriate search domain
+				// was appended, check the actual hostname.
+				
+				uint8_t actualHostname[ kDomainNameLengthMax ];
+				OSStatus nameErr = DomainNameFromString( actualHostname, actualHostnameStr, NULL );
+				if( !nameErr && DomainNameEqual( actualHostname, me->expectedHostname ) )
+				{
+					if( !gotExpiredResult )
+					{
+						if( ( resultType == dnssd_getaddrinfo_result_type_expired ) && fromCache )
+						{
+							gotExpiredResult = true;
+							resultIsExpected = true;
+						}
+					}
+					else if ( !gotAddResult )
+					{
+						if( ( resultType == dnssd_getaddrinfo_result_type_add ) && !fromCache )
+						{
+							gotAddResult     = true;
+							resultIsExpected = true;
+						}
+					}
+				}
+			}
+			if( !resultIsExpected ) ++unexpectedResultCount;
+			FPrintF( stdout,
+				"%{du:time} %s Got %sexpected result -- type: %s, hostname: %s, address: %##a, actual hostname: %s, "
+				"from cache: %s\n",
+				NULL, resultIsExpected ? "✓" : "x", resultIsExpected ? "" : "un",
+				dnssd_getaddrinfo_result_type_to_string( resultType ), dnssd_getaddrinfo_result_get_hostname( result ),
+				dnssd_getaddrinfo_result_get_address( result ), actualHostnameStr, YesNoStr( fromCache ) );
+		}
+		if( unexpectedResultCount > 0 ) _OptimisticDNSTestStop( me, kUnexpectedErr );
+	} );
+	dnssd_getaddrinfo_activate( me->gai );
+	
+	// Start the time limit timer.
+	
+	check( !me->timer );
+	me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue );
+	require_action_quiet( me->timer, exit, err = kNoResourcesErr );
+	
+	dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 );
+	dispatch_source_set_event_handler( me->timer,
+	^{
+		dispatch_source_forget( &me->timer );
+		dnssd_getaddrinfo_forget( &me->gai );
+		OSStatus finalErr;
+		if( !gotExpiredResult || !gotAddResult )
+		{
+			if( !gotExpiredResult )
+			{
+				FPrintF( stdout, "%{du:time} Failed to get EXPIRED GAI result for '%s' after %u seconds.\n",
+					NULL, me->hostname, timeLimitSecs );
+			}
+			if( !gotAddResult )
+			{
+				FPrintF( stdout, "%{du:time} Failed to get ADD GAI result for '%s' after %u seconds.\n",
+					NULL, me->hostname, timeLimitSecs );
+			}
+			finalErr = kTimeoutErr;
+		}
+		else
+		{
+			finalErr = kNoErr;
+		}
+		_OptimisticDNSTestStop( me, finalErr );
+	} );
+	dispatch_activate( me->timer );
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+
+static OSStatus	_OptimisticDNSTestStartSubtest7_1( const OptimisticDNSTestRef me )
+{
+	OSStatus err;
+	__block Boolean gotAddResult = false;
+	FPrintF( stdout,
+		"%{du:time} Subtest 7.1: First multi-label PQDN GAI for hostname that has one CNAME record and that doesn't"
+		" depend on a search domain (AlwaysAppendSearchDomains enabled)\n",
+		NULL );
+	
+	// Create the hostname.
+	
+	const uint8_t offset = 70;
+	char middleLabels[ 512 ];
+	SNPrintF( middleLabels, sizeof( middleLabels ), "ttl-600.offset-%u", offset );
+	ForgetMem( &me->hostname );
+	const char * const domainStr = mdns_domain_name_get_presentation( me->domain );
+	ASPrintF( &me->hostname, "alias-ttl-1.%s.%s", middleLabels, domainStr );
+	require_action( me->hostname, exit, err = kNoMemoryErr );
+	
+	// Make sure to remove the trailing dot to make the hostname a PQDN.
+	
+	const size_t len = strlen( me->hostname );
+	me->hostname[ len - 1 ] = '\0';
+	
+	// Set the expected IPv4 address.
+	
+	char ipv4AddrStr[ kSockAddrStringMaxSize ];
+	SNPrintF( ipv4AddrStr, sizeof( ipv4AddrStr ), "203.0.113.%u", offset + 1 );
+	err = StringToSockAddr( ipv4AddrStr, &me->expectedAddr, sizeof( me->expectedAddr ), NULL );
+	require_noerr( err, exit );
+	
+	// Since this is a GAI for a PQDN instead of a FQDN, and it doesn't depend on a search domain, to make sure that no
+	// search domain was appended, check the actual hostname, which should be a child of the domain that we used for the
+	// test DNS server. Specifically, the actual hostname should be of the form ttl-600.offset-60.<domain>.
+	//
+	// Note: We don't want results from another of the DNS server's search domains, such as dnssec.test. The domain
+	// specified with --domain should come first in the list of search domains.
+	
+	err = DomainNameFromString( me->expectedHostname, middleLabels, NULL );
+	require_noerr( err, exit );
+	
+	err = DomainNameAppendString( me->expectedHostname, domainStr, NULL );
+	require_noerr( err, exit );
+	
+	// Start the GAI operation.
+	
+	const unsigned int timeLimitSecs = 5;
+	FPrintF( stdout,
+		"%{du:time} * Starting GAI for multi-label PQDN '%s' with %u second time limit\n", NULL,
+		me->hostname, timeLimitSecs );
+	const DNSServiceFlags flags = kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsAllowExpiredAnswers;
+	me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, flags );
+	require_action( me->gai, exit, err = kNoResourcesErr );
+	
+	const dnssd_getaddrinfo_t gai = me->gai;
+	dnssd_getaddrinfo_set_result_handler( me->gai,
+	^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount )
+	{
+		require_return( me->gai == gai );
+		
+		size_t unexpectedResultCount = 0;
+		for( size_t i = 0; i < inCount; ++i )
+		{
+			Boolean resultIsExpected = false;
+			const dnssd_getaddrinfo_result_t result = inResults[ i ];
+			const dnssd_getaddrinfo_result_type_t resultType = dnssd_getaddrinfo_result_get_type( result );
+			const char * const actualHostnameStr = dnssd_getaddrinfo_result_get_actual_hostname( result );
+			const bool fromCache = dnssd_getaddrinfo_result_is_from_cache( result );
+			if( SockAddrCompareAddr( dnssd_getaddrinfo_result_get_address( result ), &me->expectedAddr.sa ) == 0 )
+			{
+				// Since this is a GAI for a single-label instead of a FQDN, to make sure that appropriate search domain
+				// was appended, check the actual hostname, which should be equal to the domain that we used for the
+				// test DNS server: alias-ttl-1.<domain> → <domain> → 203.0.113.1
+				//
+				// Note: We don't want results from another of the DNS server's search domains, such as dnssec.test. The
+				// domain specified with --domain should come first in the list of search domains.
+				
+				uint8_t actualHostname[ kDomainNameLengthMax ];
+				OSStatus nameErr = DomainNameFromString( actualHostname, actualHostnameStr, NULL );
+				if( !nameErr && DomainNameEqual( actualHostname, me->expectedHostname ) )
+				{
+					if( !gotAddResult )
+					{
+						if( ( resultType == dnssd_getaddrinfo_result_type_add ) && !fromCache )
+						{
+							gotAddResult     = true;
+							resultIsExpected = true;
+						}
+					}
+				}
+			}
+			if( !resultIsExpected ) ++unexpectedResultCount;
+			FPrintF( stdout,
+				"%{du:time} %s Got %sexpected result -- type: %s, hostname: %s, address: %##a, actual hostname: %s, "
+				"from cache: %s\n",
+				NULL, resultIsExpected ? "✓" : "x", resultIsExpected ? "" : "un",
+				dnssd_getaddrinfo_result_type_to_string( resultType ), dnssd_getaddrinfo_result_get_hostname( result ),
+				dnssd_getaddrinfo_result_get_address( result ), actualHostnameStr, YesNoStr( fromCache ) );
+		}
+		if( unexpectedResultCount > 0 ) _OptimisticDNSTestStop( me, kUnexpectedErr );
+	} );
+	dnssd_getaddrinfo_activate( me->gai );
+	
+	// Start the time limit timer.
+	
+	check( !me->timer );
+	me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue );
+	require_action_quiet( me->timer, exit, err = kNoResourcesErr );
+	
+	dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 );
+	dispatch_source_set_event_handler( me->timer,
+	^{
+		dispatch_source_forget( &me->timer );
+		dnssd_getaddrinfo_forget( &me->gai );
+		if( !gotAddResult )
+		{
+			FPrintF( stdout, "%{du:time} Failed to get ADD GAI result for '%s' after %u seconds.\n",
+				NULL, me->hostname, timeLimitSecs );
+			_OptimisticDNSTestStop( me, kTimeoutErr );
+		}
+		else
+		{
+			const unsigned int waitSecs = 15;
+			FPrintF( stdout, "%{du:time} â§– Waiting %u seconds for CNAME record to expire\n", NULL, waitSecs );
+			usleep( waitSecs * kMicrosecondsPerSecond );
+			const OSStatus startErr = _OptimisticDNSTestStartSubtest7_2( me );
+			if( startErr ) _OptimisticDNSTestStop( me, startErr );
+		}
+	} );
+	dispatch_activate( me->timer );
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+
+static OSStatus	_OptimisticDNSTestStartSubtest6_2( const OptimisticDNSTestRef me )
+{
+	OSStatus err;
+	__block Boolean gotExpiredResult = false;
+	__block Boolean gotAddResult = false;
+	FPrintF( stdout,
+		"%{du:time} Subtest 6.2: Second multi-label PQDN GAI for hostname that has one CNAME record and that depends on a"
+		" search domain (CNAME record expired, AlwaysAppendSearchDomains enabled)\n",
+		NULL );
+	
+	// Start the GAI operation.
+	
+	check( me->hostname );
+	const unsigned int timeLimitSecs = 5;
+	FPrintF( stdout, "%{du:time} * Starting GAI for multi-label PQDN '%s' with %u second time limit\n",
+		NULL, me->hostname, timeLimitSecs );
+	const DNSServiceFlags flags = kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsAllowExpiredAnswers;
+	me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, flags );
+	require_action( me->gai, exit, err = kNoResourcesErr );
+	
+	const dnssd_getaddrinfo_t gai = me->gai;
+	dnssd_getaddrinfo_set_result_handler( me->gai,
+	^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount )
+	{
+		require_return( me->gai == gai );
+		
+		size_t unexpectedResultCount = 0;
+		for( size_t i = 0; i < inCount; ++i )
+		{
+			Boolean resultIsExpected = false;
+			const dnssd_getaddrinfo_result_t result = inResults[ i ];
+			const dnssd_getaddrinfo_result_type_t resultType = dnssd_getaddrinfo_result_get_type( result );
+			const char * const actualHostnameStr = dnssd_getaddrinfo_result_get_actual_hostname( result );
+			const bool fromCache = dnssd_getaddrinfo_result_is_from_cache( result );
+			if( SockAddrCompareAddr( dnssd_getaddrinfo_result_get_address( result ), &me->expectedAddr.sa ) == 0 )
+			{
+				uint8_t actualHostname[ kDomainNameLengthMax ];
+				OSStatus nameErr = DomainNameFromString( actualHostname, actualHostnameStr, NULL );
+				if( !nameErr && DomainNameEqual( actualHostname, me->expectedHostname ) )
+				{
+					if( !gotExpiredResult )
+					{
+						if( ( resultType == dnssd_getaddrinfo_result_type_expired ) && fromCache )
+						{
+							gotExpiredResult = true;
+							resultIsExpected = true;
+						}
+					}
+					else if ( !gotAddResult )
+					{
+						if( ( resultType == dnssd_getaddrinfo_result_type_add ) && !fromCache )
+						{
+							gotAddResult     = true;
+							resultIsExpected = true;
+						}
+					}
+				}
+			}
+			if( !resultIsExpected ) ++unexpectedResultCount;
+			FPrintF( stdout,
+				"%{du:time} %s Got %sexpected result -- type: %s, hostname: %s, address: %##a, actual hostname: %s, "
+				"from cache: %s\n",
+				NULL, resultIsExpected ? "✓" : "x", resultIsExpected ? "" : "un",
+				dnssd_getaddrinfo_result_type_to_string( resultType ), dnssd_getaddrinfo_result_get_hostname( result ),
+				dnssd_getaddrinfo_result_get_address( result ), actualHostnameStr, YesNoStr( fromCache ) );
+		}
+		if( unexpectedResultCount > 0 ) _OptimisticDNSTestStop( me, kUnexpectedErr );
+	} );
+	dnssd_getaddrinfo_activate( me->gai );
+	
+	// Start the time limit timer.
+	
+	check( !me->timer );
+	me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue );
+	require_action_quiet( me->timer, exit, err = kNoResourcesErr );
+	
+	dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 );
+	dispatch_source_set_event_handler( me->timer,
+	^{
+		dispatch_source_forget( &me->timer );
+		dnssd_getaddrinfo_forget( &me->gai );
+		if( !gotExpiredResult || !gotAddResult )
+		{
+			if( !gotExpiredResult )
+			{
+				FPrintF( stdout, "%{du:time} Failed to get EXPIRED GAI result for '%s' after %u seconds.\n",
+					NULL, me->hostname, timeLimitSecs );
+			}
+			if( !gotAddResult )
+			{
+				FPrintF( stdout, "%{du:time} Failed to get ADD GAI result for '%s' after %u seconds.\n",
+					NULL, me->hostname, timeLimitSecs );
+			}
+			_OptimisticDNSTestStop( me, kTimeoutErr );
+		}
+		else
+		{
+			const OSStatus startErr = _OptimisticDNSTestStartSubtest7_1( me );
+			if( startErr ) _OptimisticDNSTestStop( me, startErr );
+		}
+	} );
+	dispatch_activate( me->timer );
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+
+static OSStatus	_OptimisticDNSTestStartSubtest6_1( const OptimisticDNSTestRef me )
+{
+	OSStatus err;
+	__block Boolean gotAddResult = false;
+	
+	// Check if the test should proceed with subtests that require that mDNSResponder be killed.
+	
+	if( !me->fullTest )
+	{
+		FPrintF( stdout, "%{du:time} âš  Not proceeding with subtests that require mDNSResponder to be killed\n", NULL );
+		_OptimisticDNSTestRetain( me );
+		dispatch_async( me->queue,
+		^{
+			_OptimisticDNSTestStop( me, kNoErr );
+			_OptimisticDNSTestRelease( me );
+		} );
+		err = kNoErr;
+		goto exit;
+	}
+	FPrintF( stdout,
+		"%{du:time} Subtest 6.1: First multi-label PQDN GAI for hostname that has one CNAME record and that depends on a"
+		" search domain (AlwaysAppendSearchDomains enabled)\n",
+		NULL );
+	
+	// Set AlwaysAppendSearchDomains to true.
+	
+	const CFStringRef appID = CFSTR( kMDNSResponderPrefAppIDStr );
+	const CFStringRef keyAASD = CFSTR( kMDNSResponderPrefStr_AlwaysAppendSearchDomains );
+	const CFPropertyListRef valueAASD = kCFBooleanTrue;
+	me->savedAASDPref = CFPreferencesCopyValue( keyAASD, appID, kCFPreferencesAnyUser, kCFPreferencesCurrentHost );
+	FPrintF( stdout, "%{du:time} âš’ Changing %@ preference: '%@' → '%@'\n", NULL, keyAASD, me->savedAASDPref, valueAASD );
+	CFPreferencesSetValue( keyAASD, valueAASD, appID, kCFPreferencesAnyUser, kCFPreferencesCurrentHost );
+	me->modifiedDaemon = true;
+	
+	// Restart mDNSResponder.
+	
+	FPrintF( stdout, "%{du:time} ↻ Restarting mDNSResponder\n", NULL );
+	err = systemf( NULL, "killall -KILL mDNSResponder" );
+	require_noerr( err, exit );
+	
+	// Give mDNSResponder some time to respawn.
+	
+	const unsigned int delaySecs = 15;
+	FPrintF( stdout, "%{du:time} â§– Waiting %u seconds to allow mDNSResponder to respawn\n", NULL, delaySecs );
+	usleep( delaySecs * kMicrosecondsPerSecond );
+	
+	// Create the hostname.
+	
+	const uint8_t offset = 60;
+	char offsetLabel[ 512 ];
+	SNPrintF( offsetLabel, sizeof( offsetLabel ), "offset-%u", offset );
+	ForgetMem( &me->hostname );
+	ASPrintF( &me->hostname, "alias-ttl-1.%s", offsetLabel );
+	require_action( me->hostname, exit, err = kNoMemoryErr );
+	
+	// Set the expected IPv4 address.
+	
+	char ipv4AddrStr[ kSockAddrStringMaxSize ];
+	SNPrintF( ipv4AddrStr, sizeof( ipv4AddrStr ), "203.0.113.%u", offset + 1 );
+	err = StringToSockAddr( ipv4AddrStr, &me->expectedAddr, sizeof( me->expectedAddr ), NULL );
+	require_noerr( err, exit );
+	
+	// Since this is a GAI for a PQDN instead of a FQDN, and it depends on a search domain, to make sure that
+	// the appropriate search domain was appended, check the actual hostname, which should be a child of the domain
+	// that we used for the test DNS server. Specifically, the actual hostname should be of the form offset-50.<domain>.
+	//
+	// Note: We don't want results from another of the DNS server's search domains, such as dnssec.test. The domain
+	// specified with --domain should come first in the list of search domains.
+	
+	err = DomainNameFromString( me->expectedHostname, offsetLabel, NULL );
+	require_noerr( err, exit );
+	
+	err = DomainNameAppendString( me->expectedHostname, mdns_domain_name_get_presentation( me->domain ), NULL );
+	require_noerr( err, exit );
+	
+	// Start the GAI operation.
+	
+	const unsigned int timeLimitSecs = 5;
+	FPrintF( stdout,
+		"%{du:time} * Starting GAI for multi-label PQDN '%s' with %u second time limit\n", NULL,
+		me->hostname, timeLimitSecs );
+	const DNSServiceFlags flags = kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsAllowExpiredAnswers;
+	me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, flags );
+	require_action( me->gai, exit, err = kNoResourcesErr );
+	
+	const dnssd_getaddrinfo_t gai = me->gai;
+	dnssd_getaddrinfo_set_result_handler( me->gai,
+	^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount )
+	{
+		require_return( me->gai == gai );
+		
+		size_t unexpectedResultCount = 0;
+		for( size_t i = 0; i < inCount; ++i )
+		{
+			Boolean resultIsExpected = false;
+			const dnssd_getaddrinfo_result_t result = inResults[ i ];
+			const dnssd_getaddrinfo_result_type_t resultType = dnssd_getaddrinfo_result_get_type( result );
+			const char * const actualHostnameStr = dnssd_getaddrinfo_result_get_actual_hostname( result );
+			const bool fromCache = dnssd_getaddrinfo_result_is_from_cache( result );
+			if( SockAddrCompareAddr( dnssd_getaddrinfo_result_get_address( result ), &me->expectedAddr.sa ) == 0 )
+			{
+				uint8_t actualHostname[ kDomainNameLengthMax ];
+				OSStatus nameErr = DomainNameFromString( actualHostname, actualHostnameStr, NULL );
+				if( !nameErr && DomainNameEqual( actualHostname, me->expectedHostname ) )
+				{
+					if( !gotAddResult )
+					{
+						if( ( resultType == dnssd_getaddrinfo_result_type_add ) && !fromCache )
+						{
+							gotAddResult     = true;
+							resultIsExpected = true;
+						}
+					}
+				}
+			}
+			if( !resultIsExpected ) ++unexpectedResultCount;
+			FPrintF( stdout,
+				"%{du:time} %s Got %sexpected result -- type: %s, hostname: %s, address: %##a, actual hostname: %s, "
+				"from cache: %s\n",
+				NULL, resultIsExpected ? "✓" : "x", resultIsExpected ? "" : "un",
+				dnssd_getaddrinfo_result_type_to_string( resultType ), dnssd_getaddrinfo_result_get_hostname( result ),
+				dnssd_getaddrinfo_result_get_address( result ), actualHostnameStr, YesNoStr( fromCache ) );
+		}
+		if( unexpectedResultCount > 0 ) _OptimisticDNSTestStop( me, kUnexpectedErr );
+	} );
+	dnssd_getaddrinfo_activate( me->gai );
+	
+	// Start the time limit timer.
+	
+	check( !me->timer );
+	me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue );
+	require_action_quiet( me->timer, exit, err = kNoResourcesErr );
+	
+	dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 );
+	dispatch_source_set_event_handler( me->timer,
+	^{
+		dispatch_source_forget( &me->timer );
+		dnssd_getaddrinfo_forget( &me->gai );
+		if( !gotAddResult )
+		{
+			FPrintF( stdout, "%{du:time} Failed to get ADD GAI result for '%s' after %u seconds.\n",
+				NULL, me->hostname, timeLimitSecs );
+			_OptimisticDNSTestStop( me, kTimeoutErr );
+		}
+		else
+		{
+			const unsigned int waitSecs = 15;
+			FPrintF( stdout, "%{du:time} â§– Waiting %u seconds for CNAME record to expire\n", NULL, waitSecs );
+			usleep( waitSecs * kMicrosecondsPerSecond );
+			const OSStatus startErr = _OptimisticDNSTestStartSubtest6_2( me );
+			if( startErr ) _OptimisticDNSTestStop( me, startErr );
+		}
+	} );
+	dispatch_activate( me->timer );
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+
+static OSStatus	_OptimisticDNSTestStartSubtest5_2( const OptimisticDNSTestRef me )
+{
+	OSStatus err;
+	__block Boolean gotExpiredResult = false;
+	__block Boolean gotAddResult = false;
+	FPrintF( stdout,
+		"%{du:time} Subtest 5.2: Second single-label GAI for hostname with CNAME record"
+		" (appends search domain, CNAME record expired)\n",
+		NULL );
+	check( me->hostname );
+	const unsigned int timeLimitSecs = 5;
+	FPrintF( stdout, "%{du:time} * Starting GAI for single-label '%s' with %u second time limit\n",
+		NULL, me->hostname, timeLimitSecs );
+	const DNSServiceFlags flags = kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsAllowExpiredAnswers;
+	me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, flags );
+	require_action( me->gai, exit, err = kNoResourcesErr );
+	
+	const dnssd_getaddrinfo_t gai = me->gai;
+	dnssd_getaddrinfo_set_result_handler( me->gai,
+	^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount )
+	{
+		require_return( me->gai == gai );
+		
+		size_t unexpectedResultCount = 0;
+		for( size_t i = 0; i < inCount; ++i )
+		{
+			Boolean resultIsExpected = false;
+			const dnssd_getaddrinfo_result_t result = inResults[ i ];
+			const dnssd_getaddrinfo_result_type_t resultType = dnssd_getaddrinfo_result_get_type( result );
+			const char * const actualHostnameStr = dnssd_getaddrinfo_result_get_actual_hostname( result );
+			const bool fromCache = dnssd_getaddrinfo_result_is_from_cache( result );
+			if( SockAddrCompareAddr( dnssd_getaddrinfo_result_get_address( result ), &me->expectedAddr.sa ) == 0 )
+			{
+				uint8_t actualHostname[ kDomainNameLengthMax ];
+				OSStatus nameErr = DomainNameFromString( actualHostname, actualHostnameStr, NULL );
+				if( !nameErr && DomainNameEqual( actualHostname, me->expectedHostname ) )
+				{
+					if( !gotExpiredResult )
+					{
+						if( ( resultType == dnssd_getaddrinfo_result_type_expired ) && fromCache )
+						{
+							gotExpiredResult = true;
+							resultIsExpected = true;
+						}
+					}
+					else if ( !gotAddResult )
+					{
+						if( ( resultType == dnssd_getaddrinfo_result_type_add ) && !fromCache )
+						{
+							gotAddResult     = true;
+							resultIsExpected = true;
+						}
+					}
+				}
+			}
+			if( !resultIsExpected ) ++unexpectedResultCount;
+			FPrintF( stdout,
+				"%{du:time} %s Got %sexpected result -- type: %s, hostname: %s, address: %##a, actual hostname: %s, "
+				"from cache: %s\n",
+				NULL, resultIsExpected ? "✓" : "x", resultIsExpected ? "" : "un",
+				dnssd_getaddrinfo_result_type_to_string( resultType ), dnssd_getaddrinfo_result_get_hostname( result ),
+				dnssd_getaddrinfo_result_get_address( result ), actualHostnameStr, YesNoStr( fromCache ) );
+		}
+		if( unexpectedResultCount > 0 ) _OptimisticDNSTestStop( me, kUnexpectedErr );
+	} );
+	dnssd_getaddrinfo_activate( me->gai );
+	
+	check( !me->timer );
+	me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue );
+	require_action_quiet( me->timer, exit, err = kNoResourcesErr );
+	
+	dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 );
+	dispatch_source_set_event_handler( me->timer,
+	^{
+		dispatch_source_forget( &me->timer );
+		dnssd_getaddrinfo_forget( &me->gai );
+		if( !gotExpiredResult || !gotAddResult )
+		{
+			if( !gotExpiredResult )
+			{
+				FPrintF( stdout, "%{du:time} Failed to get EXPIRED GAI result for '%s' after %u seconds.\n",
+					NULL, me->hostname, timeLimitSecs );
+			}
+			if( !gotAddResult )
+			{
+				FPrintF( stdout, "%{du:time} Failed to get ADD GAI result for '%s' after %u seconds.\n",
+					NULL, me->hostname, timeLimitSecs );
+			}
+			_OptimisticDNSTestStop( me, kTimeoutErr );
+		}
+		else
+		{
+			const OSStatus startErr = _OptimisticDNSTestStartSubtest6_1( me );
+			if( startErr ) _OptimisticDNSTestStop( me, startErr );
+		}
+	} );
+	dispatch_activate( me->timer );
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+
+static OSStatus	_OptimisticDNSTestStartSubtest5_1( const OptimisticDNSTestRef me )
+{
+	OSStatus err;
+	__block Boolean gotAddResult = false;
+	FPrintF( stdout,
+		"%{du:time} Subtest 5.1: First single-label GAI for hostname with CNAME record (appends search domain)\n",
+		NULL );
+	ForgetMem( &me->hostname );
+	ASPrintF( &me->hostname, "%s", "alias-ttl-1" );
+	require_action( me->hostname, exit, err = kNoMemoryErr );
+	
+	// Since this is a GAI for a single-label PQDN instead of a FQDN, and it depends on a search domain, to make sure
+	// that the appropriate search domain was appended, check the actual hostname, which should be a child of the domain
+	// that we used for the test DNS server. Specifically, the actual hostname should be <domain>.
+	//
+	// Note: We don't want results from another of the DNS server's search domains, such as dnssec.test. The domain
+	// specified with --domain should come first in the list of search domains.
+	
+	err = DomainNameFromString( me->expectedHostname, mdns_domain_name_get_presentation( me->domain ), NULL );
+	require_noerr( err, exit );
+	
+	// Set the expected IPv4 address.
+	
+	err = StringToSockAddr( "203.0.113.1", &me->expectedAddr, sizeof( me->expectedAddr ), NULL );
+	require_noerr( err, exit );
+	
+	const unsigned int timeLimitSecs = 5;
+	FPrintF( stdout,
+		"%{du:time} * Starting GAI for single-label '%s' with %u second time limit\n", NULL, me->hostname, timeLimitSecs );
+	const DNSServiceFlags flags = kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsAllowExpiredAnswers;
+	me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, flags );
+	require_action( me->gai, exit, err = kNoResourcesErr );
+	
+	const dnssd_getaddrinfo_t gai = me->gai;
+	dnssd_getaddrinfo_set_result_handler( me->gai,
+	^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount )
+	{
+		require_return( me->gai == gai );
+		
+		size_t unexpectedResultCount = 0;
+		for( size_t i = 0; i < inCount; ++i )
+		{
+			Boolean resultIsExpected = false;
+			const dnssd_getaddrinfo_result_t result = inResults[ i ];
+			const dnssd_getaddrinfo_result_type_t resultType = dnssd_getaddrinfo_result_get_type( result );
+			const char * const actualHostnameStr = dnssd_getaddrinfo_result_get_actual_hostname( result );
+			const bool fromCache = dnssd_getaddrinfo_result_is_from_cache( result );
+			if( SockAddrCompareAddr( dnssd_getaddrinfo_result_get_address( result ), &me->expectedAddr.sa ) == 0 )
+			{
+				uint8_t actualHostname[ kDomainNameLengthMax ];
+				OSStatus nameErr = DomainNameFromString( actualHostname, actualHostnameStr, NULL );
+				if( !nameErr && DomainNameEqual( actualHostname, me->expectedHostname ) )
+				{
+					if( !gotAddResult )
+					{
+						if( ( resultType == dnssd_getaddrinfo_result_type_add ) && !fromCache )
+						{
+							gotAddResult     = true;
+							resultIsExpected = true;
+						}
+					}
+				}
+			}
+			if( !resultIsExpected ) ++unexpectedResultCount;
+			FPrintF( stdout,
+				"%{du:time} %s Got %sexpected result -- type: %s, hostname: %s, address: %##a, actual hostname: %s, "
+				"from cache: %s\n",
+				NULL, resultIsExpected ? "✓" : "x", resultIsExpected ? "" : "un",
+				dnssd_getaddrinfo_result_type_to_string( resultType ), dnssd_getaddrinfo_result_get_hostname( result ),
+				dnssd_getaddrinfo_result_get_address( result ), actualHostnameStr, YesNoStr( fromCache ) );
+		}
+		if( unexpectedResultCount > 0 ) _OptimisticDNSTestStop( me, kUnexpectedErr );
+	} );
+	dnssd_getaddrinfo_activate( me->gai );
+	
+	check( !me->timer );
+	me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue );
+	require_action_quiet( me->timer, exit, err = kNoResourcesErr );
+	
+	dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 );
+	dispatch_source_set_event_handler( me->timer,
+	^{
+		dispatch_source_forget( &me->timer );
+		dnssd_getaddrinfo_forget( &me->gai );
+		if( !gotAddResult )
+		{
+			FPrintF( stdout, "%{du:time} Failed to get ADD GAI result for '%s' after %u seconds.\n",
+				NULL, me->hostname, timeLimitSecs );
+			_OptimisticDNSTestStop( me, kTimeoutErr );
+		}
+		else
+		{
+			const unsigned int waitSecs = 15;
+			FPrintF( stdout, "%{du:time} â§– Waiting %u seconds for CNAME record to expire\n", NULL, waitSecs );
+			usleep( waitSecs * kMicrosecondsPerSecond );
+			const OSStatus startErr = _OptimisticDNSTestStartSubtest5_2( me );
+			if( startErr ) _OptimisticDNSTestStop( me, startErr );
+		}
+	} );
+	dispatch_activate( me->timer );
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+
+static OSStatus	_OptimisticDNSTestStartSubtest4_2( const OptimisticDNSTestRef me )
+{
+	OSStatus err;
+	__block Boolean gotExpiredResult = false;
+	__block Boolean gotAddResult = false;
+	FPrintF( stdout, "%{du:time} Subtest 4.2: Second GAI for hostname with one CNAME record (A record expired)\n", NULL );
+	check( me->hostname );
+	const unsigned int timeLimitSecs = 5;
+	FPrintF( stdout, "%{du:time} * Starting GAI for '%s' with %u second time limit\n", NULL, me->hostname, timeLimitSecs );
+	const DNSServiceFlags flags = kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsAllowExpiredAnswers;
+	me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, flags );
+	require_action( me->gai, exit, err = kNoResourcesErr );
+	
+	const dnssd_getaddrinfo_t gai = me->gai;
+	dnssd_getaddrinfo_set_result_handler( me->gai,
+	^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount )
+	{
+		require_return( me->gai == gai );
+		
+		size_t unexpectedResultCount = 0;
+		for( size_t i = 0; i < inCount; ++i )
+		{
+			Boolean resultIsExpected = false;
+			const dnssd_getaddrinfo_result_t result = inResults[ i ];
+			const dnssd_getaddrinfo_result_type_t resultType = dnssd_getaddrinfo_result_get_type( result );
+			const bool fromCache = dnssd_getaddrinfo_result_is_from_cache( result );
+			if( SockAddrCompareAddr( dnssd_getaddrinfo_result_get_address( result ), &me->expectedAddr.sa ) == 0 )
+			{
+				if( !gotExpiredResult )
+				{
+					if( ( resultType == dnssd_getaddrinfo_result_type_expired ) && fromCache )
+					{
+						gotExpiredResult = true;
+						resultIsExpected = true;
+					}
+				}
+				else if ( !gotAddResult )
+				{
+					if( ( resultType == dnssd_getaddrinfo_result_type_add ) && !fromCache )
+					{
+						gotAddResult     = true;
+						resultIsExpected = true;
+					}
+				}
+			}
+			if( !resultIsExpected ) ++unexpectedResultCount;
+			FPrintF( stdout,
+				"%{du:time} %s Got %sexpected result -- type: %s, hostname: %s, address: %##a, from cache: %s\n",
+				NULL, resultIsExpected ? "✓" : "x", resultIsExpected ? "" : "un",
+				dnssd_getaddrinfo_result_type_to_string( resultType ), dnssd_getaddrinfo_result_get_hostname( result ),
+				dnssd_getaddrinfo_result_get_address( result ), YesNoStr( fromCache ) );
+		}
+		if( unexpectedResultCount > 0 ) _OptimisticDNSTestStop( me, kUnexpectedErr );
+	} );
+	dnssd_getaddrinfo_activate( me->gai );
+	
+	check( !me->timer );
+	me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue );
+	require_action_quiet( me->timer, exit, err = kNoResourcesErr );
+	
+	dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 );
+	dispatch_source_set_event_handler( me->timer,
+	^{
+		dispatch_source_forget( &me->timer );
+		dnssd_getaddrinfo_forget( &me->gai );
+		if( !gotExpiredResult || !gotAddResult )
+		{
+			if( !gotExpiredResult )
+			{
+				FPrintF( stdout, "%{du:time} Failed to get EXPIRED GAI result for '%s' after %u seconds.\n",
+					NULL, me->hostname, timeLimitSecs );
+			}
+			if( !gotAddResult )
+			{
+				FPrintF( stdout, "%{du:time} Failed to get ADD GAI result for '%s' after %u seconds.\n",
+					NULL, me->hostname, timeLimitSecs );
+			}
+			_OptimisticDNSTestStop( me, kTimeoutErr );
+		}
+		else
+		{
+			const OSStatus startErr = _OptimisticDNSTestStartSubtest5_1( me );
+			if( startErr ) _OptimisticDNSTestStop( me, startErr );
+		}
+	} );
+	dispatch_activate( me->timer );
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+
+static OSStatus	_OptimisticDNSTestStartSubtest4_1( const OptimisticDNSTestRef me )
+{
+	OSStatus err;
+	__block Boolean gotAddResult = false;
+	FPrintF( stdout, "%{du:time} Subtest 4.1: First GAI for hostname with one CNAME record\n", NULL );
+	
+	// Create the hostname.
+	
+	const uint8_t offset = 40;
+	ForgetMem( &me->hostname );
+	ASPrintF( &me->hostname,
+		"alias-ttl-600.tag-address-record-expires-first.ttl-1.offset-%u.%s",
+		offset, mdns_domain_name_get_presentation( me->domain ) );
+	require_action( me->hostname, exit, err = kNoMemoryErr );
+	
+	// Set the expected IPv4 address.
+	
+	char ipv4AddrStr[ kSockAddrStringMaxSize ];
+	SNPrintF( ipv4AddrStr, sizeof( ipv4AddrStr ), "203.0.113.%u", offset + 1 );
+	err = StringToSockAddr( ipv4AddrStr, &me->expectedAddr, sizeof( me->expectedAddr ), NULL );
+	require_noerr( err, exit );
+	
+	const unsigned int timeLimitSecs = 5;
+	FPrintF( stdout, "%{du:time} * Starting GAI for '%s' with %u second time limit\n", NULL, me->hostname, timeLimitSecs );
+	const DNSServiceFlags flags = kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsAllowExpiredAnswers;
+	me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, flags );
+	require_action( me->gai, exit, err = kNoResourcesErr );
+	
+	const dnssd_getaddrinfo_t gai = me->gai;
+	dnssd_getaddrinfo_set_result_handler( me->gai,
+	^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount )
+	{
+		require_return( me->gai == gai );
+		
+		size_t unexpectedResultCount = 0;
+		for( size_t i = 0; i < inCount; ++i )
+		{
+			Boolean resultIsExpected = false;
+			const dnssd_getaddrinfo_result_t result = inResults[ i ];
+			const dnssd_getaddrinfo_result_type_t resultType = dnssd_getaddrinfo_result_get_type( result );
+			const bool fromCache = dnssd_getaddrinfo_result_is_from_cache( result );
+			if( SockAddrCompareAddr( dnssd_getaddrinfo_result_get_address( result ), &me->expectedAddr.sa ) == 0 )
+			{
+				if( !gotAddResult )
+				{
+					if( ( resultType == dnssd_getaddrinfo_result_type_add ) && !fromCache )
+					{
+						gotAddResult     = true;
+						resultIsExpected = true;
+					}
+				}
+			}
+			if( !resultIsExpected ) ++unexpectedResultCount;
+			FPrintF( stdout,
+				"%{du:time} %s Got %sexpected result -- type: %s, hostname: %s, address: %##a, from cache: %s\n",
+				NULL, resultIsExpected ? "✓" : "x", resultIsExpected ? "" : "un",
+				dnssd_getaddrinfo_result_type_to_string( resultType ), dnssd_getaddrinfo_result_get_hostname( result ),
+				dnssd_getaddrinfo_result_get_address( result ), YesNoStr( fromCache ) );
+		}
+		if( unexpectedResultCount > 0 ) _OptimisticDNSTestStop( me, kUnexpectedErr );
+	} );
+	dnssd_getaddrinfo_activate( me->gai );
+	
+	check( !me->timer );
+	me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue );
+	require_action_quiet( me->timer, exit, err = kNoResourcesErr );
+	
+	dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 );
+	dispatch_source_set_event_handler( me->timer,
+	^{
+		dispatch_source_forget( &me->timer );
+		dnssd_getaddrinfo_forget( &me->gai );
+		if( !gotAddResult )
+		{
+			FPrintF( stdout, "%{du:time} Failed to get ADD GAI result for '%s' after %u seconds.\n",
+				NULL, me->hostname, timeLimitSecs );
+			_OptimisticDNSTestStop( me, kTimeoutErr );
+		}
+		else
+		{
+			const unsigned int waitSecs = 15;
+			FPrintF( stdout, "%{du:time} â§– Waiting %u seconds for A record to expire\n", NULL, waitSecs );
+			usleep( waitSecs * kMicrosecondsPerSecond );
+			const OSStatus startErr = _OptimisticDNSTestStartSubtest4_2( me );
+			if( startErr ) _OptimisticDNSTestStop( me, startErr );
+		}
+	} );
+	dispatch_activate( me->timer );
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+
+static OSStatus	_OptimisticDNSTestStartSubtest3_2( const OptimisticDNSTestRef me )
+{
+	OSStatus err;
+	__block Boolean gotExpiredResult = false;
+	__block Boolean gotAddResult = false;
+	FPrintF( stdout,
+		"%{du:time} Subtest 3.2: Second GAI for hostname with two CNAME records (2nd CNAME record expired)\n", NULL );
+	check( me->hostname );
+	const unsigned int timeLimitSecs = 5;
+	FPrintF( stdout, "%{du:time} * Starting GAI for '%s' with %u second time limit\n", NULL, me->hostname, timeLimitSecs );
+	const DNSServiceFlags flags = kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsAllowExpiredAnswers;
+	me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, flags );
+	require_action( me->gai, exit, err = kNoResourcesErr );
+	
+	const dnssd_getaddrinfo_t gai = me->gai;
+	dnssd_getaddrinfo_set_result_handler( me->gai,
+	^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount )
+	{
+		require_return( me->gai == gai );
+		
+		size_t unexpectedResultCount = 0;
+		for( size_t i = 0; i < inCount; ++i )
+		{
+			Boolean resultIsExpected = false;
+			const dnssd_getaddrinfo_result_t result = inResults[ i ];
+			const dnssd_getaddrinfo_result_type_t resultType = dnssd_getaddrinfo_result_get_type( result );
+			const bool fromCache = dnssd_getaddrinfo_result_is_from_cache( result );
+			if( SockAddrCompareAddr( dnssd_getaddrinfo_result_get_address( result ), &me->expectedAddr.sa ) == 0 )
+			{
+				if( !gotExpiredResult )
+				{
+					if( ( resultType == dnssd_getaddrinfo_result_type_expired ) && fromCache )
+					{
+						gotExpiredResult = true;
+						resultIsExpected = true;
+					}
+				}
+				else if ( !gotAddResult )
+				{
+					if( ( resultType == dnssd_getaddrinfo_result_type_add ) && !fromCache )
+					{
+						gotAddResult     = true;
+						resultIsExpected = true;
+					}
+				}
+			}
+			if( !resultIsExpected ) ++unexpectedResultCount;
+			FPrintF( stdout,
+				"%{du:time} %s Got %sexpected result -- type: %s, hostname: %s, address: %##a, from cache: %s\n",
+				NULL, resultIsExpected ? "✓" : "x", resultIsExpected ? "" : "un",
+				dnssd_getaddrinfo_result_type_to_string( resultType ), dnssd_getaddrinfo_result_get_hostname( result ),
+				dnssd_getaddrinfo_result_get_address( result ), YesNoStr( fromCache ) );
+		}
+		if( unexpectedResultCount > 0 ) _OptimisticDNSTestStop( me, kUnexpectedErr );
+	} );
+	dnssd_getaddrinfo_activate( me->gai );
+	
+	check( !me->timer );
+	me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue );
+	require_action_quiet( me->timer, exit, err = kNoResourcesErr );
+	
+	dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 );
+	dispatch_source_set_event_handler( me->timer,
+	^{
+		dispatch_source_forget( &me->timer );
+		dnssd_getaddrinfo_forget( &me->gai );
+		if( !gotExpiredResult || !gotAddResult )
+		{
+			if( !gotExpiredResult )
+			{
+				FPrintF( stdout, "%{du:time} Failed to get EXPIRED GAI result for '%s' after %u seconds.\n",
+					NULL, me->hostname, timeLimitSecs );
+			}
+			if( !gotAddResult )
+			{
+				FPrintF( stdout, "%{du:time} Failed to get ADD GAI result for '%s' after %u seconds.\n",
+					NULL, me->hostname, timeLimitSecs );
+			}
+			_OptimisticDNSTestStop( me, kTimeoutErr );
+		}
+		else
+		{
+			const OSStatus startErr = _OptimisticDNSTestStartSubtest4_1( me );
+			if( startErr ) _OptimisticDNSTestStop( me, startErr );
+		}
+	} );
+	dispatch_activate( me->timer );
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+
+static OSStatus	_OptimisticDNSTestStartSubtest3_1( const OptimisticDNSTestRef me )
+{
+	OSStatus err;
+	__block Boolean gotAddResult = false;
+	FPrintF( stdout, "%{du:time} Subtest 3.1: First GAI for hostname with two CNAME records\n", NULL );
+	
+	// Create the hostname.
+	
+	const uint8_t offset = 30;
+	ForgetMem( &me->hostname );
+	ASPrintF( &me->hostname,
+		"alias-ttl-600-1.tag-second-cname-expires-first.ttl-600.offset-%u.%s",
+		offset, mdns_domain_name_get_presentation( me->domain ) );
+	require_action( me->hostname, exit, err = kNoMemoryErr );
+	
+	// Set the expected IPv4 address.
+	
+	char ipv4AddrStr[ kSockAddrStringMaxSize ];
+	SNPrintF( ipv4AddrStr, sizeof( ipv4AddrStr ), "203.0.113.%u", offset + 1 );
+	err = StringToSockAddr( ipv4AddrStr, &me->expectedAddr, sizeof( me->expectedAddr ), NULL );
+	require_noerr( err, exit );
+	
+	const unsigned int timeLimitSecs = 5;
+	FPrintF( stdout, "%{du:time} * Starting GAI for '%s' with %u second time limit\n", NULL, me->hostname, timeLimitSecs );
+	const DNSServiceFlags flags = kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsAllowExpiredAnswers;
+	me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, flags );
+	require_action( me->gai, exit, err = kNoResourcesErr );
+	
+	const dnssd_getaddrinfo_t gai = me->gai;
+	dnssd_getaddrinfo_set_result_handler( me->gai,
+	^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount )
+	{
+		require_return( me->gai == gai );
+		
+		size_t unexpectedResultCount = 0;
+		for( size_t i = 0; i < inCount; ++i )
+		{
+			Boolean resultIsExpected = false;
+			const dnssd_getaddrinfo_result_t result = inResults[ i ];
+			const dnssd_getaddrinfo_result_type_t resultType = dnssd_getaddrinfo_result_get_type( result );
+			const bool fromCache = dnssd_getaddrinfo_result_is_from_cache( result );
+			if( SockAddrCompareAddr( dnssd_getaddrinfo_result_get_address( result ), &me->expectedAddr.sa ) == 0 )
+			{
+				if( !gotAddResult )
+				{
+					if( ( resultType == dnssd_getaddrinfo_result_type_add ) && !fromCache )
+					{
+						gotAddResult     = true;
+						resultIsExpected = true;
+					}
+				}
+			}
+			if( !resultIsExpected ) ++unexpectedResultCount;
+			FPrintF( stdout,
+				"%{du:time} %s Got %sexpected result -- type: %s, hostname: %s, address: %##a, from cache: %s\n",
+				NULL, resultIsExpected ? "✓" : "x", resultIsExpected ? "" : "un",
+				dnssd_getaddrinfo_result_type_to_string( resultType ), dnssd_getaddrinfo_result_get_hostname( result ),
+				dnssd_getaddrinfo_result_get_address( result ), YesNoStr( fromCache ) );
+		}
+		if( unexpectedResultCount > 0 ) _OptimisticDNSTestStop( me, kUnexpectedErr );
+	} );
+	dnssd_getaddrinfo_activate( me->gai );
+	
+	check( !me->timer );
+	me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue );
+	require_action_quiet( me->timer, exit, err = kNoResourcesErr );
+	
+	dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 );
+	dispatch_source_set_event_handler( me->timer,
+	^{
+		dispatch_source_forget( &me->timer );
+		dnssd_getaddrinfo_forget( &me->gai );
+		if( !gotAddResult )
+		{
+			FPrintF( stdout, "%{du:time} Failed to get ADD GAI result for '%s' after %u seconds.\n",
+				NULL, me->hostname, timeLimitSecs );
+			_OptimisticDNSTestStop( me, kTimeoutErr );
+		}
+		else
+		{
+			const unsigned int waitSecs = 15;
+			FPrintF( stdout, "%{du:time} â§– Waiting %u seconds for CNAME record to expire\n", NULL, waitSecs );
+			usleep( waitSecs * kMicrosecondsPerSecond );
+			const OSStatus startErr = _OptimisticDNSTestStartSubtest3_2( me );
+			if( startErr ) _OptimisticDNSTestStop( me, startErr );
+		}
+	} );
+	dispatch_activate( me->timer );
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+
+static OSStatus	_OptimisticDNSTestStartSubtest2_2( const OptimisticDNSTestRef me )
+{
+	OSStatus err;
+	__block Boolean gotExpiredResult = false;
+	__block Boolean gotAddResult = false;
+	FPrintF( stdout,
+		"%{du:time} Subtest 2.2: Second GAI for hostname with one CNAME record (CNAME record expired)\n", NULL );
+	check( me->hostname );
+	const unsigned int timeLimitSecs = 5;
+	FPrintF( stdout, "%{du:time} * Starting GAI for '%s' with %u second time limit\n", NULL, me->hostname, timeLimitSecs );
+	const DNSServiceFlags flags = kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsAllowExpiredAnswers;
+	me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, flags );
+	require_action( me->gai, exit, err = kNoResourcesErr );
+	
+	const dnssd_getaddrinfo_t gai = me->gai;
+	dnssd_getaddrinfo_set_result_handler( me->gai,
+	^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount )
+	{
+		require_return( me->gai == gai );
+		
+		size_t unexpectedResultCount = 0;
+		for( size_t i = 0; i < inCount; ++i )
+		{
+			Boolean resultIsExpected = false;
+			const dnssd_getaddrinfo_result_t result = inResults[ i ];
+			const dnssd_getaddrinfo_result_type_t resultType = dnssd_getaddrinfo_result_get_type( result );
+			const bool fromCache = dnssd_getaddrinfo_result_is_from_cache( result );
+			if( SockAddrCompareAddr( dnssd_getaddrinfo_result_get_address( result ), &me->expectedAddr.sa ) == 0 )
+			{
+				if( !gotExpiredResult )
+				{
+					if( ( resultType == dnssd_getaddrinfo_result_type_expired ) && fromCache )
+					{
+						gotExpiredResult = true;
+						resultIsExpected = true;
+					}
+				}
+				else if ( !gotAddResult )
+				{
+					if( ( resultType == dnssd_getaddrinfo_result_type_add ) && !fromCache )
+					{
+						gotAddResult     = true;
+						resultIsExpected = true;
+					}
+				}
+			}
+			if( !resultIsExpected ) ++unexpectedResultCount;
+			FPrintF( stdout,
+				"%{du:time} %s Got %sexpected result -- type: %s, hostname: %s, address: %##a, from cache: %s\n",
+				NULL, resultIsExpected ? "✓" : "x", resultIsExpected ? "" : "un",
+				dnssd_getaddrinfo_result_type_to_string( resultType ), dnssd_getaddrinfo_result_get_hostname( result ),
+				dnssd_getaddrinfo_result_get_address( result ), YesNoStr( fromCache ) );
+		}
+		if( unexpectedResultCount > 0 ) _OptimisticDNSTestStop( me, kUnexpectedErr );
+	} );
+	dnssd_getaddrinfo_activate( me->gai );
+	
+	check( !me->timer );
+	me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue );
+	require_action_quiet( me->timer, exit, err = kNoResourcesErr );
+	
+	dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 );
+	dispatch_source_set_event_handler( me->timer,
+	^{
+		dispatch_source_forget( &me->timer );
+		dnssd_getaddrinfo_forget( &me->gai );
+		if( !gotExpiredResult || !gotAddResult )
+		{
+			if( !gotExpiredResult )
+			{
+				FPrintF( stdout, "%{du:time} Failed to get EXPIRED GAI result for '%s' after %u seconds.\n",
+					NULL, me->hostname, timeLimitSecs );
+			}
+			if( !gotAddResult )
+			{
+				FPrintF( stdout, "%{du:time} Failed to get ADD GAI result for '%s' after %u seconds.\n",
+					NULL, me->hostname, timeLimitSecs );
+			}
+			_OptimisticDNSTestStop( me, kTimeoutErr );
+		}
+		else
+		{
+			const OSStatus startErr = _OptimisticDNSTestStartSubtest3_1( me );
+			if( startErr ) _OptimisticDNSTestStop( me, startErr );
+		}
+	} );
+	dispatch_activate( me->timer );
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+
+static OSStatus	_OptimisticDNSTestStartSubtest2_1( const OptimisticDNSTestRef me )
+{
+	OSStatus err;
+	__block Boolean gotAddResult = false;
+	FPrintF( stdout, "%{du:time} Subtest 2.1: First GAI for hostname with one CNAME record\n", NULL );
+	
+	// Create the hostname.
+	
+	const uint8_t offset = 20;
+	ForgetMem( &me->hostname );
+	ASPrintF( &me->hostname,
+		"alias-ttl-1.tag-cname-expires-first.ttl-600.offset-%u.%s",
+		offset, mdns_domain_name_get_presentation( me->domain ) );
+	require_action( me->hostname, exit, err = kNoMemoryErr );
+	
+	// Set the expected IPv4 address.
+	
+	char ipv4AddrStr[ kSockAddrStringMaxSize ];
+	SNPrintF( ipv4AddrStr, sizeof( ipv4AddrStr ), "203.0.113.%u", offset + 1 );
+	err = StringToSockAddr( ipv4AddrStr, &me->expectedAddr, sizeof( me->expectedAddr ), NULL );
+	require_noerr( err, exit );
+	
+	const unsigned int timeLimitSecs = 5;
+	FPrintF( stdout, "%{du:time} * Starting GAI for '%s' with %u second time limit\n", NULL, me->hostname, timeLimitSecs );
+	const DNSServiceFlags flags = kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsAllowExpiredAnswers;
+	me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, flags );
+	require_action( me->gai, exit, err = kNoResourcesErr );
+	
+	const dnssd_getaddrinfo_t gai = me->gai;
+	dnssd_getaddrinfo_set_result_handler( me->gai,
+	^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount )
+	{
+		require_return( me->gai == gai );
+		
+		size_t unexpectedResultCount = 0;
+		for( size_t i = 0; i < inCount; ++i )
+		{
+			Boolean resultIsExpected = false;
+			const dnssd_getaddrinfo_result_t result = inResults[ i ];
+			const dnssd_getaddrinfo_result_type_t resultType = dnssd_getaddrinfo_result_get_type( result );
+			const bool fromCache = dnssd_getaddrinfo_result_is_from_cache( result );
+			if( SockAddrCompareAddr( dnssd_getaddrinfo_result_get_address( result ), &me->expectedAddr.sa ) == 0 )
+			{
+				if( !gotAddResult )
+				{
+					if( ( resultType == dnssd_getaddrinfo_result_type_add ) && !fromCache )
+					{
+						gotAddResult     = true;
+						resultIsExpected = true;
+					}
+				}
+			}
+			if( !resultIsExpected ) ++unexpectedResultCount;
+			FPrintF( stdout,
+				"%{du:time} %s Got %sexpected result -- type: %s, hostname: %s, address: %##a, from cache: %s\n",
+				NULL, resultIsExpected ? "✓" : "x", resultIsExpected ? "" : "un",
+				dnssd_getaddrinfo_result_type_to_string( resultType ), dnssd_getaddrinfo_result_get_hostname( result ),
+				dnssd_getaddrinfo_result_get_address( result ), YesNoStr( fromCache ) );
+		}
+		if( unexpectedResultCount > 0 ) _OptimisticDNSTestStop( me, kUnexpectedErr );
+	} );
+	dnssd_getaddrinfo_activate( me->gai );
+	
+	check( !me->timer );
+	me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue );
+	require_action_quiet( me->timer, exit, err = kNoResourcesErr );
+	
+	dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 );
+	dispatch_source_set_event_handler( me->timer,
+	^{
+		dispatch_source_forget( &me->timer );
+		dnssd_getaddrinfo_forget( &me->gai );
+		if( !gotAddResult )
+		{
+			FPrintF( stdout, "%{du:time} Failed to get ADD GAI result for '%s' after %u seconds.\n",
+				NULL, me->hostname, timeLimitSecs );
+			_OptimisticDNSTestStop( me, kTimeoutErr );
+		}
+		else
+		{
+			const unsigned int waitSecs = 15;
+			FPrintF( stdout, "%{du:time} â§– Waiting %u seconds for CNAME record to expire\n", NULL, waitSecs );
+			usleep( waitSecs * kMicrosecondsPerSecond );
+			const OSStatus startErr = _OptimisticDNSTestStartSubtest2_2( me );
+			if( startErr ) _OptimisticDNSTestStop( me, startErr );
+		}
+	} );
+	dispatch_activate( me->timer );
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+
+static OSStatus	_OptimisticDNSTestStartSubtest1_2( const OptimisticDNSTestRef me )
+{
+	OSStatus err;
+	__block Boolean gotExpiredResult = false;
+	__block Boolean gotAddResult = false;
+	FPrintF( stdout, "%{du:time} Subtest 1.2: Second GAI for hostname without CNAME records (A record expired)\n", NULL );
+	check( me->hostname );
+	const unsigned int timeLimitSecs = 5;
+	FPrintF( stdout,
+		"%{du:time} * Starting GAI for '%s' with %u second time limit\n", NULL, me->hostname, timeLimitSecs );
+	const DNSServiceFlags flags = kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsAllowExpiredAnswers;
+	me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, flags );
+	require_action( me->gai, exit, err = kNoResourcesErr );
+	
+	const dnssd_getaddrinfo_t gai = me->gai;
+	dnssd_getaddrinfo_set_result_handler( me->gai,
+	^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount )
+	{
+		require_return( me->gai == gai );
+		
+		size_t unexpectedResultCount = 0;
+		for( size_t i = 0; i < inCount; ++i )
+		{
+			Boolean resultIsExpected = false;
+			const dnssd_getaddrinfo_result_t result = inResults[ i ];
+			const dnssd_getaddrinfo_result_type_t resultType = dnssd_getaddrinfo_result_get_type( result );
+			const bool fromCache = dnssd_getaddrinfo_result_is_from_cache( result );
+			if( SockAddrCompareAddr( dnssd_getaddrinfo_result_get_address( result ), &me->expectedAddr.sa ) == 0 )
+			{
+				if( !gotExpiredResult )
+				{
+					if( ( resultType == dnssd_getaddrinfo_result_type_expired ) && fromCache )
+					{
+						gotExpiredResult = true;
+						resultIsExpected = true;
+					}
+				}
+				else if ( !gotAddResult )
+				{
+					if( ( resultType == dnssd_getaddrinfo_result_type_add ) && !fromCache )
+					{
+						gotAddResult     = true;
+						resultIsExpected = true;
+					}
+				}
+			}
+			if( !resultIsExpected ) ++unexpectedResultCount;
+			FPrintF( stdout,
+				"%{du:time} %s Got %sexpected result -- type: %s, hostname: %s, address: %##a, from cache: %s\n",
+				NULL, resultIsExpected ? "✓" : "x", resultIsExpected ? "" : "un",
+				dnssd_getaddrinfo_result_type_to_string( resultType ), dnssd_getaddrinfo_result_get_hostname( result ),
+				dnssd_getaddrinfo_result_get_address( result ), YesNoStr( fromCache ) );
+		}
+		if( unexpectedResultCount > 0 ) _OptimisticDNSTestStop( me, kUnexpectedErr );
+	} );
+	dnssd_getaddrinfo_activate( me->gai );
+	
+	check( !me->timer );
+	me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue );
+	require_action_quiet( me->timer, exit, err = kNoResourcesErr );
+	
+	dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 );
+	dispatch_source_set_event_handler( me->timer,
+	^{
+		dispatch_source_forget( &me->timer );
+		dnssd_getaddrinfo_forget( &me->gai );
+		if( !gotExpiredResult || !gotAddResult )
+		{
+			if( !gotExpiredResult )
+			{
+				FPrintF( stdout, "%{du:time} Failed to get EXPIRED GAI result for '%s' after %u seconds.\n",
+					NULL, me->hostname, timeLimitSecs );
+			}
+			if( !gotAddResult )
+			{
+				FPrintF( stdout, "%{du:time} Failed to get ADD GAI result for '%s' after %u seconds.\n",
+					NULL, me->hostname, timeLimitSecs );
+			}
+			_OptimisticDNSTestStop( me, kTimeoutErr );
+		}
+		else
+		{
+			const OSStatus startErr = _OptimisticDNSTestStartSubtest2_1( me );
+			if( startErr ) _OptimisticDNSTestStop( me, startErr );
+		}
+	} );
+	dispatch_activate( me->timer );
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+
+static OSStatus	_OptimisticDNSTestStartSubtest1_1( const OptimisticDNSTestRef me )
+{
+	OSStatus err;
+	__block Boolean gotAddResult = false;
+	FPrintF( stdout, "%{du:time} Subtest 1.1: First GAI for hostname without CNAME records\n", NULL );
+	
+	// Create the hostname.
+	
+	const uint8_t offset = 10;
+	ForgetMem( &me->hostname );
+	ASPrintF( &me->hostname, "ttl-1.offset-%u.%s", offset, mdns_domain_name_get_presentation( me->domain ) );
+	require_action( me->hostname, exit, err = kNoMemoryErr );
+	
+	// Set the expected IPv4 address.
+	
+	char ipv4AddrStr[ kSockAddrStringMaxSize ];
+	SNPrintF( ipv4AddrStr, sizeof( ipv4AddrStr ), "203.0.113.%u", offset + 1 );
+	err = StringToSockAddr( ipv4AddrStr, &me->expectedAddr, sizeof( me->expectedAddr ), NULL );
+	require_noerr( err, exit );
+	
+	const unsigned int timeLimitSecs = 5;
+	FPrintF( stdout, "%{du:time} * Starting GAI for '%s' with %u second time limit\n",
+		NULL, me->hostname, timeLimitSecs );
+	const DNSServiceFlags flags = kDNSServiceFlagsReturnIntermediates | kDNSServiceFlagsAllowExpiredAnswers;
+	me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, flags );
+	require_action( me->gai, exit, err = kNoResourcesErr );
+	
+	const dnssd_getaddrinfo_t gai = me->gai;
+	dnssd_getaddrinfo_set_result_handler( me->gai,
+	^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount )
+	{
+		require_return( me->gai == gai );
+		
+		size_t unexpectedResultCount = 0;
+		for( size_t i = 0; i < inCount; ++i )
+		{
+			Boolean resultIsExpected = false;
+			const dnssd_getaddrinfo_result_t result = inResults[ i ];
+			const dnssd_getaddrinfo_result_type_t resultType = dnssd_getaddrinfo_result_get_type( result );
+			const bool fromCache = dnssd_getaddrinfo_result_is_from_cache( result );
+			if( !gotAddResult )
+			{
+				if( ( resultType == dnssd_getaddrinfo_result_type_add ) && !fromCache )
+				{
+					if( SockAddrCompareAddr( dnssd_getaddrinfo_result_get_address( result ), &me->expectedAddr.sa ) == 0 )
+					{
+						gotAddResult     = true;
+						resultIsExpected = true;
+					}
+				}
+			}
+			if( !resultIsExpected ) ++unexpectedResultCount;
+			FPrintF( stdout,
+				"%{du:time} %s Got %sexpected result -- type: %s, hostname: %s, address: %##a, from cache: %s\n",
+				NULL, resultIsExpected ? "✓" : "x", resultIsExpected ? "" : "un",
+				dnssd_getaddrinfo_result_type_to_string( resultType ), dnssd_getaddrinfo_result_get_hostname( result ),
+				dnssd_getaddrinfo_result_get_address( result ), YesNoStr( fromCache ) );
+		}
+		if( unexpectedResultCount > 0 ) _OptimisticDNSTestStop( me, kUnexpectedErr );
+	} );
+	dnssd_getaddrinfo_activate( me->gai );
+	
+	check( !me->timer );
+	me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue );
+	require_action_quiet( me->timer, exit, err = kNoResourcesErr );
+	
+	dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 );
+	dispatch_source_set_event_handler( me->timer,
+	^{
+		dispatch_source_forget( &me->timer );
+		dnssd_getaddrinfo_forget( &me->gai );
+		if( !gotAddResult )
+		{
+			FPrintF( stdout, "%{du:time} Failed to get ADD GAI result for '%s' after %u seconds.\n",
+				NULL, me->hostname, timeLimitSecs );
+			_OptimisticDNSTestStop( me, kTimeoutErr );
+		}
+		else
+		{
+			const unsigned int waitSecs = 15;
+			FPrintF( stdout, "%{du:time} â§– Waiting %u seconds for A record to expire\n", NULL, waitSecs );
+			usleep( waitSecs * kMicrosecondsPerSecond );
+			const OSStatus startErr = _OptimisticDNSTestStartSubtest1_2( me );
+			if( startErr ) _OptimisticDNSTestStop( me, startErr );
+		}
+	} );
+	dispatch_activate( me->timer );
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+
+static void	_OptimisticDNSTestStart( void * const inCtx )
+{
+	OSStatus err;
+	char *domainStr = NULL;
+	char *serverCmd = NULL;
+	const OptimisticDNSTestRef me = (OptimisticDNSTestRef) inCtx;
+	char tag[ 6 + 1 ];
+	ASPrintF( &domainStr,
+		"optimistic-dns-test-%s.test.",
+		_RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag ) );
+	require_action( domainStr, exit, err = kNoMemoryErr );
+	
+	me->domain = mdns_domain_name_create( domainStr, mdns_domain_name_create_opts_none, &err );
+	require_noerr( err, exit );
+	
+	// Use the --registerSC option because this test uses GAI operations that only specify a single label as the
+	// hostname. The test depends on a search domain equal to a match domain for the test DNS server being appended to
+    // PQDNs. SystemConfiguration will set up a search domain for each of the DNS service's match domains.
+	//
+	// Also use --default to make the DNS server act as a low-priority default DNS service. Currently, a negative
+	// response from a server is required to iterate to the next search domain in the search domain list. If a test
+	// device happens to not be connected to any network, then it won't have a DHCP-assigned DNS service to act as a
+	// default DNS service that could provide a potentially negative response to move things along.
+	
+	ASPrintF( &serverCmd,
+		DNSSDUTIL_TEST_DNS_SERVER_COMMAND_PREAMBLE " --loopback --registerSC --default --follow %lld --responseDelay 10"
+		" --domain %s",
+		(int64_t) getpid(), mdns_domain_name_get_presentation( me->domain ) );
+	require_action_quiet( serverCmd, exit, err = kNoMemoryErr );
+	
+	FPrintF( stdout, "%{du:time} Starting DNS server with command: %s\n", NULL, serverCmd );
+	err = _SpawnCommand( &me->serverPID, "/dev/null", "/dev/null", "%s", serverCmd );
+	require_noerr( err, exit );
+	
+	ASPrintF( &me->hostname, "tag-probe.offset-250.%s", mdns_domain_name_get_presentation( me->domain ) );
+	require_action( me->hostname, exit, err = kNoMemoryErr );
+	
+	me->gai = _OptimisticDNSTestCreateGAI( me, me->hostname, 0 );
+	require_action( me->gai, exit, err = kNoResourcesErr );
+	
+	const unsigned int timeoutSecs = 5;
+	FPrintF( stdout,
+		"%{du:time} Starting GAI to detect DNS server readiness with probe hostname '%s' and %u second timeout\n",
+			NULL, me->hostname, timeoutSecs );
+	const dnssd_getaddrinfo_t gai = me->gai;
+	dnssd_getaddrinfo_set_result_handler( me->gai,
+	^( dnssd_getaddrinfo_result_t * const inResults, const size_t inCount )
+	{
+		require_return( me->gai == gai );
+		
+		Boolean proceed = false;
+		for( size_t i = 0; i < inCount; ++i )
+		{
+			const dnssd_getaddrinfo_result_t result = inResults[ i ];
+			if( dnssd_getaddrinfo_result_get_type( result ) == dnssd_getaddrinfo_result_type_add )
+			{
+				proceed = true;
+				break;
+			}
+		}
+		if( proceed )
+		{
+			dispatch_source_forget( &me->timer );
+			dnssd_getaddrinfo_forget( &me->gai );
+			FPrintF( stdout, "%{du:time} Starting subtests...\n", NULL );
+			const OSStatus startErr = _OptimisticDNSTestStartSubtest1_1( me );
+			if( startErr ) _OptimisticDNSTestStop( me, startErr );
+		}
+	} );
+	dnssd_getaddrinfo_activate( me->gai );
+	
+	me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue );
+	require_action_quiet( me->timer, exit, err = kNoResourcesErr );
+	
+	dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeoutSecs ), DISPATCH_TIME_FOREVER, 0 );
+	dispatch_source_set_event_handler( me->timer,
+	^{
+		FPrintF( stdout,
+			"%{du:time} Probe GAI request for '%s' timed out after %u seconds.\n", NULL, me->hostname, timeoutSecs );
+		_OptimisticDNSTestStop( me, kNotPreparedErr );
+	} );
+	dispatch_activate( me->timer );
+	
+exit:
+	ForgetMem( &domainStr );
+	ForgetMem( &serverCmd );
+	if( err ) _OptimisticDNSTestStop( me, err );
+}
+
+//===========================================================================================================================
+
+static OSStatus	_OptimisticDNSTestRun( const OptimisticDNSTestRef me )
+{
+	dispatch_async_f( me->queue, me, _OptimisticDNSTestStart );
+	dispatch_semaphore_wait( me->doneSem, DISPATCH_TIME_FOREVER );
+	return( me->error );
+}
+
+//===========================================================================================================================
+
+static void	_OptimisticDNSTestRelease( const OptimisticDNSTestRef me )
+{
+	if( atomic_add_and_fetch_32( &me->refCount, -1 ) == 0 )
+	{
+		check( !me->gai );
+		check( !me->timer );
+		check( !me->domain );
+		check( !me->hostname );
+		check( me->serverPID < 0 );
+		dispatch_forget( &me->queue );
+		dispatch_forget( &me->doneSem );
+		free( me );
+	}
+}
+
+//===========================================================================================================================
+//	RecordRegistrationTestCommand
+//===========================================================================================================================
+
+// The RRSet maximum member count is currently 32. This allows the use of 32-bit bitmaps for keeping track of RRSet
+// membership.
+
+#define kRecordRegistrationTestRRSetMemberCountMax		32
+
+typedef struct RecordRegistrationTest *		RecordRegistrationTestRef;
+struct RecordRegistrationTest
+{
+	dispatch_queue_t			queue;					// Serial queue for test events.
+	dispatch_semaphore_t		doneSem;				// Semaphore to signal when the test is done.
+	DNSServiceRef				query;					// Reference for DNSServiceQueryRecord() operation.
+	DNSServiceRef				connection;				// Connection for record registrations.
+	DNSRecordRef				recordRefs[ 32 ];		// Record registration references.
+	dispatch_source_t			timer;					// Timer for enforcing time limits.
+	char *						rrSetName;				// Name of current RRSet.
+	MDNSColliderRef				collider;				// Creates an mDNS record conflict. [1]
+	int32_t						refCount;				// Test's reference count.
+	OSStatus					error;					// Test's error code.
+	size_t						rrSetBitmapIndex;		// The current RRSet bitmap index.
+	uint32_t					rrSetBitmapObserved;	// Keeps track of RRSet members seen by DNSServiceQueryRecord().
+	size_t						subtestIndex;			// Current subtest index into kRegisterRecordSubtests array.
+	size_t						interfaceSetIndex;		// Current subtest interface set index.
+	uint32_t					registrationIfIndex;	// Current interface index to pass to DNSServiceRegisterRecord().
+	DNSServiceFlags				registrationFlags;		// Current flags to pass to DNSServiceRegisterRecord().
+	unsigned int				rrSetChangeIntervalMs;	// The time to wait in between RRSet changes.
+	uint8_t						rdataBase[ 4 ];			// The randomly-generated A record RDATA base for the RRSet.
+	Boolean						conflictOccurred;		// True if the subtest's expected record conflict has occurred. [1]
+};
+
+// Notes:
+// 1. Exclusively For conflict subtests.
+
+check_compile_time( countof_field( struct RecordRegistrationTest, recordRefs ) ==
+	kRecordRegistrationTestRRSetMemberCountMax );
+
+typedef enum
+{
+	kRecordRegistrationTestInterfaceSet_Any					= 1,	// "Any" interface, except AWDL and P2P interfaces. [1]
+	kRecordRegistrationTestInterfaceSet_Loopback			= 2,	// The loopback interface.
+	kRecordRegistrationTestInterfaceSet_LocalOnly			= 3,	// The LocalOnly pseudo-interface.
+	kRecordRegistrationTestInterfaceSet_AnyPlusAWDL			= 4,	// "Any" interface plus AWDL interface. [2]
+	kRecordRegistrationTestInterfaceSet_AnyPlusP2P			= 5,	// "Any" interface plus P2P interface. [3]
+	kRecordRegistrationTestInterfaceSet_AnyPlusAWDLAndP2P	= 6,	// "Any" interface plus AWDL and P2P interfaces. [4]
+	
+}	RecordRegistrationTestInterfaceSet;
+
+// Notes:
+// 1. Uses kDNSServiceInterfaceIndexAny.
+// 2. Uses kDNSServiceInterfaceIndexAny along with kDNSServiceFlagsIncludeAWDL.
+// 3. Uses kDNSServiceInterfaceIndexAny along with kDNSServiceFlagsIncludeP2P.
+// 4. Uses kDNSServiceInterfaceIndexAny along with kDNSServiceFlagsIncludeAWDL and kDNSServiceFlagsIncludeP2P.
+
+typedef enum
+{
+	kRecordRegistrationTestRegistrationType_Shared			= 1,	// Shared record (use kDNSServiceFlagsShared).
+	kRecordRegistrationTestRegistrationType_Unique			= 2,	// Unique record (use kDNSServiceFlagsUnique).
+	kRecordRegistrationTestRegistrationType_KnownUnique		= 3,	// KnownUnique record (use kDNSServiceFlagsKnownUnique).
+	
+}	RecordRegistrationTestRegistrationType;
+
+typedef enum
+{
+	kRecordRegistrationSubtestType_ChangingRRSet	= 1,	// Subtest that deals with an RRSet that changes over time.
+	kRecordRegistrationSubtestType_Conflict			= 2,	// Subtest that deals with an RRSet that experiences a conflict.
+	
+}	RecordRegistrationSubtestType;
+
+typedef struct
+{
+	RecordRegistrationTestRegistrationType			registrationType;	// Type of record registrations.
+	const RecordRegistrationTestInterfaceSet *		interfaceSets;		// Interface sets to use for registrations.
+	size_t											interfaceSetCount;	// Number of interface sets.
+	const uint32_t *								rrSetBitmaps;		// The different instances of the RRSet to register.
+	size_t											rrSetBitmapCount;	// Number of RRSet bitmaps.
+	RecordRegistrationSubtestType					type;				// Type of subtest.
+	
+}	RegisterRecordSubtest;
+
+static const RecordRegistrationTestInterfaceSet kRecordRegistrationTestAllInterfaceSets[] =
+{
+	kRecordRegistrationTestInterfaceSet_Any,
+	kRecordRegistrationTestInterfaceSet_Loopback,
+	kRecordRegistrationTestInterfaceSet_LocalOnly,
+	kRecordRegistrationTestInterfaceSet_AnyPlusAWDL,
+	kRecordRegistrationTestInterfaceSet_AnyPlusP2P,
+	kRecordRegistrationTestInterfaceSet_AnyPlusAWDLAndP2P,
+};
+
+static const RecordRegistrationTestInterfaceSet kRecordRegistrationTestAllInterfaceSetsExceptLocalOnly[] =
+{
+	kRecordRegistrationTestInterfaceSet_Any,
+	kRecordRegistrationTestInterfaceSet_Loopback,
+	kRecordRegistrationTestInterfaceSet_AnyPlusAWDL,
+	kRecordRegistrationTestInterfaceSet_AnyPlusP2P,
+	kRecordRegistrationTestInterfaceSet_AnyPlusAWDLAndP2P,
+};
+
+#define _BitU32( N )		( UINT32_C( 1 ) << (N) )
+
+// The following array defines the different instances of a test RRSet. Each bit represents the presence or absence of a
+// member of the RRSet (1 means present, 0 means absent). The test RRSet consists of A records with the same name and
+// class. If the RRSet's name is register-record-test-wfpx79.local, and the A records are offsets from a base IPv4
+// address of 203.0.113.0, then a bitmap of 0x0000002A represents the RRSet with three members (1, 3, and 5):
+//
+//		register-record-test-wfpx79.local IN A 203.0.113.1
+//		register-record-test-wfpx79.local IN A 203.0.113.3
+//		register-record-test-wfpx79.local IN A 203.0.113.5
+//
+// The test actualizes each RRSet instance by making calls to DNSServiceRegisterRecord() and DNSServiceRemoveRecord() to
+// transform the previous RRSet instance. The first RRSet instance is simply the product of DNSServiceRegisterRecord()
+// calls.
+
+static const uint32_t		kRecordRegistrationTestRRSetBitmaps_ChanginRRSet[] =
+{
+	_BitU32( 1 ),												// RRSet with members {1}
+	_BitU32( 1 ) | _BitU32( 2 ),								// RRSet with members {1, 2}		(Add one member)
+	_BitU32( 1 ) | _BitU32( 2 ) | _BitU32( 3 ),					// RRSet with members {1, 2, 3}		(Add one member)
+	_BitU32( 2 ) | _BitU32( 3 ),								// RRSet with members {2, 3}		(Remove one member)
+	_BitU32( 2 ) | _BitU32( 3 ) | _BitU32( 4 ) | _BitU32( 5 ),	// RRSet with members {2, 3, 4, 5}	(Add two members)
+	_BitU32( 3 ) | _BitU32( 5 ),								// RRSet with members {3, 5}		(Remove two members)
+	_BitU32( 3 ) | _BitU32( 6 ),								// RRSet with members {3, 6}		(Add one, Remove one)
+	_BitU32( 7 ) | _BitU32( 8 ),								// RRSet with members {7, 8}		(Add two, Remove two)
+	_BitU32( 7 ),												// RRSet with members {7}			(Remove one member)
+	0,															// RRSet with members {}			(Remove one → empty set)
+	_BitU32( 9 ) | _BitU32( 10 ),								// RRSet with members {9, 10}		(Add two to empty set)
+	0,															// RRSet with members {}			(Remove two → empty set)
+};
+
+static const uint32_t		kRecordRegistrationTestConflictRRsetBitmaps_SingleRRSet[] =
+{
+	_BitU32( 1 ), // RRSet with one member.
+};
+
+static const RegisterRecordSubtest		kRegisterRecordSubtests[] =
+{
+	{
+		.type				= kRecordRegistrationSubtestType_ChangingRRSet,
+		.registrationType	= kRecordRegistrationTestRegistrationType_Unique,
+		.interfaceSets		= kRecordRegistrationTestAllInterfaceSets,
+		.interfaceSetCount	= countof( kRecordRegistrationTestAllInterfaceSets ),
+		.rrSetBitmaps		= kRecordRegistrationTestRRSetBitmaps_ChanginRRSet,
+		.rrSetBitmapCount	= countof( kRecordRegistrationTestRRSetBitmaps_ChanginRRSet ),
+	},
+	{
+		.type				= kRecordRegistrationSubtestType_ChangingRRSet,
+		.registrationType	= kRecordRegistrationTestRegistrationType_KnownUnique,
+		.interfaceSets		= kRecordRegistrationTestAllInterfaceSets,
+		.interfaceSetCount	= countof( kRecordRegistrationTestAllInterfaceSets ),
+		.rrSetBitmaps		= kRecordRegistrationTestRRSetBitmaps_ChanginRRSet,
+		.rrSetBitmapCount	= countof( kRecordRegistrationTestRRSetBitmaps_ChanginRRSet ),
+	},
+	{
+		.type				= kRecordRegistrationSubtestType_ChangingRRSet,
+		.registrationType	= kRecordRegistrationTestRegistrationType_Shared,
+		.interfaceSets		= kRecordRegistrationTestAllInterfaceSets,
+		.interfaceSetCount	= countof( kRecordRegistrationTestAllInterfaceSets ),
+		.rrSetBitmaps		= kRecordRegistrationTestRRSetBitmaps_ChanginRRSet,
+		.rrSetBitmapCount	= countof( kRecordRegistrationTestRRSetBitmaps_ChanginRRSet ),
+	},
+	{
+		.type				= kRecordRegistrationSubtestType_Conflict,
+		.registrationType	= kRecordRegistrationTestRegistrationType_Unique,
+		.interfaceSets		= kRecordRegistrationTestAllInterfaceSetsExceptLocalOnly,
+		.interfaceSetCount	= countof( kRecordRegistrationTestAllInterfaceSetsExceptLocalOnly ),
+		.rrSetBitmaps		= kRecordRegistrationTestConflictRRsetBitmaps_SingleRRSet,
+		.rrSetBitmapCount	= countof( kRecordRegistrationTestConflictRRsetBitmaps_SingleRRSet ),
+	},
+	{
+		.type				= kRecordRegistrationSubtestType_Conflict,
+		.registrationType	= kRecordRegistrationTestRegistrationType_KnownUnique,
+		.interfaceSets		= kRecordRegistrationTestAllInterfaceSetsExceptLocalOnly,
+		.interfaceSetCount	= countof( kRecordRegistrationTestAllInterfaceSetsExceptLocalOnly ),
+		.rrSetBitmaps		= kRecordRegistrationTestConflictRRsetBitmaps_SingleRRSet,
+		.rrSetBitmapCount	= countof( kRecordRegistrationTestConflictRRsetBitmaps_SingleRRSet ),
+	},
+};
+
+//===========================================================================================================================
+
+static void	_RecordRegistrationTestRelease( const RecordRegistrationTestRef me )
+{
+	if( atomic_add_and_fetch_32( &me->refCount, -1 ) == 0 )
+	{
+		dispatch_forget( &me->queue );
+		dispatch_forget( &me->doneSem );
+		check( !me->query );
+		check( !me->connection );
+		for( size_t i = 0; i < countof( me->recordRefs ); ++i )
+		{
+			check( !me->recordRefs[ i ] );
+		}
+		check( !me->timer );
+		ForgetMem( &me->rrSetName );
+		free( me );
+	}
+}
+
+//===========================================================================================================================
+
+static RecordRegistrationTestRef
+	_RecordRegistrationTestCreate(
+		const unsigned int	inRRSetChangeIntervalMs,
+		OSStatus * const	outError )
+{
+	OSStatus err;
+	RecordRegistrationTestRef test = NULL;
+	RecordRegistrationTestRef obj = (RecordRegistrationTestRef) calloc( 1, sizeof( *obj ) );
+	require_action( obj, exit, err = kNoMemoryErr );
+	
+	obj->refCount				= 1;
+	obj->error					= kInProgressErr;
+	obj->rrSetChangeIntervalMs	= inRRSetChangeIntervalMs;
+	obj->queue = dispatch_queue_create( "com.apple.dnssdutil.record-registration-test", DISPATCH_QUEUE_SERIAL );
+	require_action( obj->queue, exit, err = kNoResourcesErr );
+	
+	obj->doneSem = dispatch_semaphore_create( 0 );
+	require_action( obj->doneSem, exit, err = kNoResourcesErr );
+	
+	test = obj;
+	obj = NULL;
+	err = kNoErr;
+	
+exit:
+	if( outError ) *outError = err;
+	if( obj ) _RecordRegistrationTestRelease( obj );
+	return( test );
+}
+
+//===========================================================================================================================
+
+static void	_RecordRegistrationTestTearDownDNSServiceRefs( const RecordRegistrationTestRef me )
+{
+	// Forget the DNSServiceQueryRecord() operation.
+	
+	DNSServiceForget( &me->query );
+	
+	// Forget the connection for record registrations, then NULL out record registration references, which are now invalid.
+	
+	DNSServiceForget( &me->connection );
+	for( size_t i = 0; i < countof( me->recordRefs ); ++i )
+	{
+		me->recordRefs[ i ] = NULL;
+	}
+}
+
+//===========================================================================================================================
+
+static void	_RecordRegistrationTestStop( const RecordRegistrationTestRef me, const OSStatus inError )
+{
+	me->error = inError;
+	if( !me->error )
+	{
+		FPrintF( stdout, "%{du:time} Test PASSED\n", NULL );
+	}
+	else
+	{
+		FPrintF( stdout, "%{du:time} Test FAILED: %#m\n", NULL, me->error );
+	}
+	_RecordRegistrationTestTearDownDNSServiceRefs( me );
+	MDNSColliderForget( &me->collider );
+	dispatch_source_forget( &me->timer );
+	ForgetMem( &me->rrSetName );
+	dispatch_semaphore_signal( me->doneSem );
+}
+
+//===========================================================================================================================
+
+static const RegisterRecordSubtest *	_RecordRegistrationTestGetCurrentSubtest( const RecordRegistrationTestRef me )
+{
+	require_fatal( me->subtestIndex < countof( kRegisterRecordSubtests ),
+		"Invalid subtest index %zu >= %zu", me->subtestIndex, countof( kRegisterRecordSubtests ) );
+	return &kRegisterRecordSubtests[ me->subtestIndex ];
+}
+
+//===========================================================================================================================
+
+static RecordRegistrationSubtestType	_RecordRegistrationTestGetCurrentSubtestType( const RecordRegistrationTestRef me )
+{
+	const RegisterRecordSubtest * const subtest = _RecordRegistrationTestGetCurrentSubtest( me );
+	return subtest->type;
+}
+
+//===========================================================================================================================
+
+static void
+	_RecordRegistrationTestRegisterRecordCallback(
+		__unused const DNSServiceRef	inSDRef,
+		const DNSRecordRef				inRecordRef,
+		__unused const DNSServiceFlags	inFlags,
+		const DNSServiceErrorType		inError,
+		void * const					inCtx )
+{
+	OSStatus err;
+	size_t i;
+	Boolean foundRecordRef = false;
+	const RecordRegistrationTestRef me = (RecordRegistrationTestRef) inCtx;
+	for( i = 0; i < countof( me->recordRefs ); ++i )
+	{
+		if( me->recordRefs[ i ] == inRecordRef )
+		{
+			foundRecordRef = true;
+			break;
+		}
+	}
+	const RecordRegistrationSubtestType subtestType = _RecordRegistrationTestGetCurrentSubtestType( me );
+	if( foundRecordRef )
+	{
+		me->rdataBase[ 3 ] = (uint8_t) i;
+		if( inError )
+		{
+			err = inError;
+			if( subtestType == kRecordRegistrationSubtestType_Conflict )
+			{
+				if( me->collider && ( inError == kDNSServiceErr_NameConflict ) )
+				{
+					FPrintF( stdout, "%{du:time} ✓ Got expected conflict for record registration: %s IN A %.4a\n",
+						NULL, me->rrSetName, me->rdataBase );
+					me->conflictOccurred = true;
+					err = kNoErr;
+				}
+			}
+			if( err )
+			{
+				FPrintF( stdout, "%{du:time} x Failed to register record: %s IN A %.4a\n",
+					NULL, me->rrSetName, me->rdataBase );
+			}
+		}
+		else
+		{
+			FPrintF( stdout, "%{du:time} + Registered record: %s IN A %.4a\n", NULL, me->rrSetName, me->rdataBase );
+			err = kNoErr;
+		}
+	}
+	else
+	{
+		FPrintF( stdout, "%{du:time} x Got register record callback for unrecognized record reference\n", NULL );
+		err = kUnexpectedErr;
+	}
+	if( err ) _RecordRegistrationTestStop( me, err );
+}
+
+//===========================================================================================================================
+
+static OSStatus
+	_RecordRegistrationTestRegisterRecord(
+		const RecordRegistrationTestRef	me,
+		const uint8_t					inMemberID,
+		DNSRecordRef * const			outRecordRef )
+{
+	me->rdataBase[ 3 ] = inMemberID;
+	FPrintF( stdout, "%{du:time} ± Registering record: %s IN A %.4a\n", NULL, me->rrSetName, me->rdataBase );
+	const OSStatus err = DNSServiceRegisterRecord( me->connection, outRecordRef, me->registrationFlags,
+		me->registrationIfIndex, me->rrSetName, kDNSServiceType_A, kDNSServiceClass_IN, sizeof( me->rdataBase ),
+		me->rdataBase, 1 * kSecondsPerHour, _RecordRegistrationTestRegisterRecordCallback, me );
+	return( err );
+}
+
+//===========================================================================================================================
+
+static void	_RecordRegistrationTestRetain( const RecordRegistrationTestRef me )
+{
+	atomic_add_32( &me->refCount, 1 );
+}
+
+//===========================================================================================================================
+
+static RecordRegistrationTestInterfaceSet
+	_RecordRegistrationTestGetCurrentInterfaceSet(
+		const RecordRegistrationTestRef	me )
+{
+	const RegisterRecordSubtest * const subtest = _RecordRegistrationTestGetCurrentSubtest( me );
+	require_fatal( me->interfaceSetIndex < subtest->interfaceSetCount,
+		"Invalid interface set index %zu >= %zu", me->subtestIndex, subtest->interfaceSetCount );
+	return subtest->interfaceSets[ me->interfaceSetIndex ];
+}
+
+//===========================================================================================================================
+
+static RecordRegistrationTestRegistrationType
+	_RecordRegistrationTestGetCurrentRegistrationType(
+		const RecordRegistrationTestRef	me )
+{
+	const RegisterRecordSubtest * const subtest = _RecordRegistrationTestGetCurrentSubtest( me );
+	return subtest->registrationType;
+}
+
+//===========================================================================================================================
+
+static uint32_t	_RecordRegistrationTestGetCurrentRRSetBitmap( const RecordRegistrationTestRef me )
+{
+	const RegisterRecordSubtest * const subtest = _RecordRegistrationTestGetCurrentSubtest( me );
+	require_fatal( me->rrSetBitmapIndex < subtest->rrSetBitmapCount,
+		"Invalid RRSet bitmap index %zu >= %zu", me->rrSetBitmapIndex, subtest->rrSetBitmapCount );
+	return subtest->rrSetBitmaps[ me->rrSetBitmapIndex ];
+}
+
+//===========================================================================================================================
+
+static uint32_t	_RecordRegistrationTestGetLoopbackInterfaceIndex( void )
+{
+	static uint32_t sIfIndex = 0;
+	if( sIfIndex == 0 )
+	{
+		const char * const loopbackIfName = "lo0";
+		sIfIndex = if_nametoindex( loopbackIfName );
+		require_fatal( sIfIndex != 0, "Failed to get loopback interface (%s) index", loopbackIfName );
+	}
+	return( sIfIndex );
+}
+
+//===========================================================================================================================
+
+static OSStatus	_RecordRegistrationTestTriggerConflict( const RecordRegistrationTestRef me )
+{
+	OSStatus err;
+	MDNSColliderRef collider = NULL;
+	require_action_quiet( !me->collider, exit, err = kNoErr );
+	
+	// AAAA RDATA for IPv6 address 2001:db8::dead:beef.
+	
+	const uint8_t rdata[ 16 ] =
+	{
+		0x20, 0x01, 0x0D, 0xB8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xDE, 0xAD, 0xBE, 0xEF
+	};
+	FPrintF( stdout, "%{du:time} ! Scheduling send of conflicting record: %s IN AAAA %.16a\n", NULL, me->rrSetName, rdata );
+	err = MDNSColliderCreate( me->queue, &collider );
+	require_noerr( err, exit );
+	
+	err = MDNSColliderSetProgram( collider, "probes 10r; send; wait 5000" );
+	require_noerr( err, exit );
+	
+	uint8_t rrSetName[ kDomainNameLengthMax ];
+	err = DomainNameFromString( rrSetName, me->rrSetName, NULL );
+	require_noerr( err, exit );
+	
+	// The subtest's RRSet consists of A records, but a AAAA record with the same name and class is sufficient to cause
+	// a conflict. See rdar://3483349 (Any rrtype is a conflict for unique records).
+	
+	err = MDNSColliderSetRecord( collider, rrSetName, kDNSServiceType_AAAA, rdata, sizeof( rdata ) );
+	require_noerr( err, exit );
+	
+	MDNSColliderSetProtocols( collider, kMDNSColliderProtocol_IPv4 );
+	MDNSColliderSetInterfaceIndex( collider, _RecordRegistrationTestGetLoopbackInterfaceIndex() );
+	
+	err = MDNSColliderStart( collider );
+	require_noerr( err, exit );
+	
+	me->collider = collider;
+	collider = NULL;
+	
+exit:
+	CFForget( &collider );
+	return( err );
+}
+
+//===========================================================================================================================
+
+static void DNSSD_API
+	_RecordRegistrationTestQueryRecordCallback(
+		__unused const DNSServiceRef	inSDRef,
+		const DNSServiceFlags			inFlags,
+		__unused const uint32_t			inIfIndex,
+		const DNSServiceErrorType		inError,
+		const char * const				inFullName,
+		const uint16_t					inType,
+		const uint16_t					inClass,
+		const uint16_t					inRDataLen,
+		const void * const				inRDataPtr,
+		__unused const uint32_t			inTTL,
+		void * const					inCtx )
+{
+	OSStatus err;
+	const RecordRegistrationTestRef me = (RecordRegistrationTestRef) inCtx;
+	
+	// Print result.
+	
+	char ifNameBuf[ kInterfaceNameBufLen ];
+	const char * const ifName = InterfaceIndexToName( inIfIndex, ifNameBuf );
+	char typeBuf[ 32 ];
+	const char *typeStr = DNSRecordTypeValueToString( inType );
+	if( !typeStr )
+	{
+		SNPrintF( typeBuf, sizeof( typeBuf ), "TYPE%u", inType );
+		typeStr = typeBuf;
+	}
+	char classBuf[ 32 ];
+	const char *classStr;
+	if( inClass == kDNSServiceClass_IN )
+	{
+		classStr = "IN";
+	}
+	else
+	{
+		SNPrintF( classBuf, sizeof( classBuf ), "CLASS%u", inClass );
+		classStr = classBuf;
+	}
+	char *rdataStr = NULL;
+	DNSRecordDataToString( inRDataPtr, inRDataLen, inType, &rdataStr );
+	if( !rdataStr )
+	{
+		ASPrintF( &rdataStr, "%#H", inRDataPtr, (int) inRDataLen, (int) inRDataLen );
+		require_action( rdataStr, exit, err = kNoMemoryErr );
+	}
+	FPrintF( stdout, "%{du:time} → QueryRecord result -- flags: 0x%0X (%s), interface: %s/%d, error: %#m, name: %s,"
+		" type: %s, class: %s, ttl: %u, rdata: %s\n",
+		NULL, inFlags, (inFlags & kDNSServiceFlagsAdd) ? "Add" : "Rmv", ifName ? ifName : "", (int32_t) inIfIndex, inError,
+		inFullName, typeStr, classStr, inTTL, rdataStr );
+	
+	// Check callback arguments.
+	
+	uint8_t fullName[ kDomainNameLengthMax ];
+	err = DomainNameFromString( fullName, inFullName, NULL );
+	require_noerr_quiet( err, exit );
+	
+	uint8_t rrSetName[ kDomainNameLengthMax ];
+	err = DomainNameFromString( rrSetName, me->rrSetName, NULL );
+	require_noerr_quiet( err, exit );
+	
+	const Boolean nameMatch = DomainNameEqual( fullName, rrSetName );
+	require_action( nameMatch, exit, err = kNameErr );
+	require_action( inType == kDNSServiceType_A, exit, err = kTypeErr );
+	require_action( inClass == kDNSServiceClass_IN, exit, err = kTypeErr );
+	require_action( inRDataLen == 4, exit, err = kSizeErr );
+	
+	const int cmp = memcmp( inRDataPtr, me->rdataBase, 3 );
+	require_action( cmp == 0, exit, err = kValueErr );
+	
+	const uint8_t memberID = ( (const uint8_t *) inRDataPtr )[ 3 ];
+	require_action( memberID < kRecordRegistrationTestRRSetMemberCountMax, exit, err = kValueErr );
+	
+	err = inError;
+	require_noerr( err, exit );
+	
+	// Keep track of adds and removes making sure we don't get any unexpected adds or removes in the process.
+	
+	const uint32_t rrSetBitmap = _RecordRegistrationTestGetCurrentRRSetBitmap( me );
+	const uint32_t bitmask = _BitU32( memberID );
+	const RecordRegistrationSubtestType subtestType = _RecordRegistrationTestGetCurrentSubtestType( me );
+	if( inFlags & kDNSServiceFlagsAdd )
+	{
+		if( ( subtestType == kRecordRegistrationSubtestType_Conflict ) && me->conflictOccurred )
+		{
+			FPrintF( stdout, "%{du:time} x Got an ADD query result after the expected conflict\n", NULL );
+			err = kUnexpectedErr;
+			goto exit;
+		}
+		require_action( rrSetBitmap & bitmask, exit, err = kUnexpectedErr );
+		require_action( ( me->rrSetBitmapObserved & bitmask ) == 0, exit, err = kDuplicateErr );
+		
+		me->rrSetBitmapObserved |= bitmask;
+		if( subtestType == kRecordRegistrationSubtestType_Conflict )
+		{
+			// If all members of the RRSet have been observed, proceed with the conflict.
+			
+			if( me->rrSetBitmapObserved == rrSetBitmap )
+			{
+				_RecordRegistrationTestTriggerConflict( me );
+			}
+		}
+	}
+	else
+	{
+		// During Changing RRSet subtests, getting a remove for members of the last registerered RRSet is considered
+		// incorrect behavior because that's the very RRSet that we're expected to observe via the QueryRecord
+		// operation.
+		//
+		// During Conflict RRSet subtests, this is also considered incorrect behavior, but only before the expected
+		// conflict occurs. After the expected conflict, the removes are expected since the conflict is supposed to
+		// flush the RRSet from the record cache.
+		
+		if( ( subtestType == kRecordRegistrationSubtestType_ChangingRRSet ) ||
+			( ( subtestType == kRecordRegistrationSubtestType_Conflict ) && !me->conflictOccurred ) )
+		{
+			require_action( ( rrSetBitmap & bitmask ) == 0, exit, err = kUnexpectedErr );
+		}
+		require_action( ( me->rrSetBitmapObserved & bitmask ) != 0, exit, err = kUnexpectedErr );
+		
+		me->rrSetBitmapObserved &= ~bitmask;
+	}
+	
+exit:
+	ForgetMem( &rdataStr );
+	if( err ) _RecordRegistrationTestStop( me, err );
+}
+
+//===========================================================================================================================
+
+static const char *	_RecordRegistrationTestInterfaceSetToString( const RecordRegistrationTestInterfaceSet inInterfaceSet )
+{
+	switch( inInterfaceSet )
+	{
+		case kRecordRegistrationTestInterfaceSet_Any:				return( "Any Interface" );
+		case kRecordRegistrationTestInterfaceSet_Loopback:			return( "Loopback Interface" );
+		case kRecordRegistrationTestInterfaceSet_LocalOnly:			return( "LocalOnly Pseudo-Interface" );
+		case kRecordRegistrationTestInterfaceSet_AnyPlusAWDL:		return( "Any+AWDL Interfaces" );
+		case kRecordRegistrationTestInterfaceSet_AnyPlusP2P:		return( "Any+P2P Interfaces" );
+		case kRecordRegistrationTestInterfaceSet_AnyPlusAWDLAndP2P:	return( "Any+AWDL+P2P Interfaces" );
+	}
+	return( "«INVALID INTERFACE SET»" );
+}
+
+//===========================================================================================================================
+
+static const char *	_RecordRegistrationTestRegistrationTypeToString( const RecordRegistrationTestRegistrationType inRegType )
+{
+	switch( inRegType )
+	{
+		case kRecordRegistrationTestRegistrationType_Shared:		return( "Shared" );
+		case kRecordRegistrationTestRegistrationType_Unique:		return( "Unique" );
+		case kRecordRegistrationTestRegistrationType_KnownUnique:	return( "KnownUnique" );
+	}
+	return( "«INVALID REGISTRATION TYPE»" );
+}
+
+//===========================================================================================================================
+
+static const char *	_RecordRegistrationSubtestTypeToString( const RecordRegistrationSubtestType inSubtestType )
+{
+	switch( inSubtestType )
+	{
+		case kRecordRegistrationSubtestType_ChangingRRSet:	return("Changing RRSet");
+		case kRecordRegistrationSubtestType_Conflict:		return("Conflict");
+	}
+	return( "«INVALID SUBTEST TYPE»" );
+}
+
+//===========================================================================================================================
+
+static OSStatus	_RecordRegistrationTestActualizeRRSet( RecordRegistrationTestRef inTest );
+
+static OSStatus	_RecordRegistrationTestStartRegistrationsAndQuery( const RecordRegistrationTestRef me )
+{
+	// Clean up previous DNSServiceRefs and collider.
+	
+	_RecordRegistrationTestTearDownDNSServiceRefs( me );
+	MDNSColliderForget( &me->collider );
+	me->conflictOccurred = false;
+	
+	const RecordRegistrationSubtestType subtestType = _RecordRegistrationTestGetCurrentSubtestType( me );
+	switch( subtestType )
+	{
+		case kRecordRegistrationSubtestType_ChangingRRSet:
+		case kRecordRegistrationSubtestType_Conflict:
+			break;
+		
+		default:
+			FatalErrorF( "Unhandled RecordRegistrationSubtestType value %d", subtestType );
+	}
+	
+	// Select interface indexes for DNSServiceRegisterRecord() and DNSServiceQueryRecord().
+	
+	uint32_t queryRecordIfIndex;
+	const RecordRegistrationTestInterfaceSet interfaceSet = _RecordRegistrationTestGetCurrentInterfaceSet( me );
+	switch( interfaceSet )
+	{
+		case kRecordRegistrationTestInterfaceSet_Any:
+		case kRecordRegistrationTestInterfaceSet_AnyPlusAWDL:
+		case kRecordRegistrationTestInterfaceSet_AnyPlusP2P:
+		case kRecordRegistrationTestInterfaceSet_AnyPlusAWDLAndP2P:
+			me->registrationIfIndex = kDNSServiceInterfaceIndexAny;
+			queryRecordIfIndex = _RecordRegistrationTestGetLoopbackInterfaceIndex();
+			break;
+		
+		case kRecordRegistrationTestInterfaceSet_Loopback:
+			me->registrationIfIndex = _RecordRegistrationTestGetLoopbackInterfaceIndex();
+			queryRecordIfIndex = _RecordRegistrationTestGetLoopbackInterfaceIndex();
+			break;
+		
+		case kRecordRegistrationTestInterfaceSet_LocalOnly:
+			me->registrationIfIndex = kDNSServiceInterfaceIndexLocalOnly;
+			queryRecordIfIndex = kDNSServiceInterfaceIndexLocalOnly;
+			break;
+		
+		default:
+			FatalErrorF( "Unhandled RecordRegistrationTestInterfaceSet value %d", interfaceSet );
+	}
+	
+	// Set up flags for DNSServiceRegisterRecord().
+	
+	me->registrationFlags = 0;
+	switch( interfaceSet )
+	{
+		case kRecordRegistrationTestInterfaceSet_AnyPlusAWDL:
+			me->registrationFlags |= kDNSServiceFlagsIncludeAWDL;
+			break;
+		
+		case kRecordRegistrationTestInterfaceSet_AnyPlusP2P:
+			me->registrationFlags |= kDNSServiceFlagsIncludeP2P;
+			break;
+		
+		case kRecordRegistrationTestInterfaceSet_AnyPlusAWDLAndP2P:
+			me->registrationFlags |= ( kDNSServiceFlagsIncludeAWDL | kDNSServiceFlagsIncludeP2P );
+			break;
+		
+		case kRecordRegistrationTestInterfaceSet_Any:
+		case kRecordRegistrationTestInterfaceSet_Loopback:
+		case kRecordRegistrationTestInterfaceSet_LocalOnly:
+			break;
+	}
+	const RecordRegistrationTestRegistrationType registrationType = _RecordRegistrationTestGetCurrentRegistrationType( me );
+	switch( registrationType )
+	{
+		case kRecordRegistrationTestRegistrationType_Shared:
+			me->registrationFlags |= kDNSServiceFlagsShared;
+			break;
+		
+		case kRecordRegistrationTestRegistrationType_Unique:
+			me->registrationFlags |= kDNSServiceFlagsUnique;
+			break;
+		
+		case kRecordRegistrationTestRegistrationType_KnownUnique:
+			me->registrationFlags |= kDNSServiceFlagsKnownUnique;
+			break;
+		
+		default:
+			FatalErrorF( "Unhandled RecordRegistrationTestRegistrationType value %d", registrationType );
+	}
+	FPrintF( stdout, "%{du:time} Subtest %zu.%zu: Registering %s Records on %s (%s)\n",
+		NULL, me->subtestIndex + 1, me->interfaceSetIndex + 1,
+		_RecordRegistrationTestRegistrationTypeToString( registrationType ),
+		_RecordRegistrationTestInterfaceSetToString( interfaceSet ), _RecordRegistrationSubtestTypeToString( subtestType ) );
+	char ifNameBuf[ kInterfaceNameBufLen ];
+	const char * const ifName = InterfaceIndexToName( me->registrationIfIndex, ifNameBuf );
+	FPrintF( stdout, "%{du:time} Record Registration interface and flags -- interface: %s/%d, flags: %#{flags}\n",
+		NULL, ifName ? ifName : "", (int32_t) me->registrationIfIndex, me->registrationFlags, kDNSServiceFlagsDescriptors );
+	
+	// Set up connection for record registrations.
+	
+	FPrintF( stdout, "%{du:time} * Creating connection\n", NULL );
+	OSStatus err = DNSServiceCreateConnection( &me->connection );
+	require_noerr( err, exit );
+	
+	err = DNSServiceSetDispatchQueue( me->connection, me->queue );
+	require_noerr( err, exit );
+	
+	// Create a randomly-generated record name.
+	
+	char tag[ 6 + 1 ];
+	_RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag );
+	ForgetMem( &me->rrSetName );
+	ASPrintF( &me->rrSetName, "register-record-test-%s.local", tag );
+	require_action( me->rrSetName, exit, err = kNoMemoryErr );
+	
+	// Randomly-generate RDATA base for A record RDATA.
+	
+	RandomBytes( me->rdataBase, sizeof( me->rdataBase ) );
+	
+	// Actualize the RRSet.
+	
+	me->rrSetBitmapIndex = 0;
+	err = _RecordRegistrationTestActualizeRRSet( me );
+	require_noerr( err, exit );
+	
+	// Start the query for the RRSet.
+	
+	me->rrSetBitmapObserved = 0;
+	err = DNSServiceQueryRecord( &me->query, 0, queryRecordIfIndex, me->rrSetName, kDNSServiceType_A, kDNSServiceClass_IN,
+		_RecordRegistrationTestQueryRecordCallback, me );
+	require_noerr( err, exit );
+	
+	err = DNSServiceSetDispatchQueue( me->query, me->queue );
+	require_noerr( err, exit );
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+
+static OSStatus	_RecordRegistrationTestSubtestStart( const RecordRegistrationTestRef me )
+{
+	OSStatus err;
+	if( me->subtestIndex >= countof( kRegisterRecordSubtests ) )
+	{
+		_RecordRegistrationTestRetain( me );
+		dispatch_async( me->queue,
+		^{
+			_RecordRegistrationTestStop( me, kNoErr );
+			_RecordRegistrationTestRelease( me );
+		} );
+		err = kNoErr;
+		goto exit;
+	}
+	me->interfaceSetIndex = 0;
+	err = _RecordRegistrationTestStartRegistrationsAndQuery( me );
+	require_noerr( err, exit );
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+
+static OSStatus	_RecordRegistrationTestHandleTimeout( const RecordRegistrationTestRef me )
+{
+	uint32_t rrSetBitmapExpected;
+	const RecordRegistrationSubtestType subtestType = _RecordRegistrationTestGetCurrentSubtestType( me );
+	if( subtestType == kRecordRegistrationSubtestType_Conflict )
+	{
+		rrSetBitmapExpected = 0; // A conflict should cause all members of the RRSet to be flushed from the cache.
+	}
+	else
+	{
+		rrSetBitmapExpected = _RecordRegistrationTestGetCurrentRRSetBitmap( me );
+	}
+	FPrintF( stdout, "%{du:time} â§– Expected RRSet = {", NULL );
+	const char *separator = "";
+	for( unsigned int i = 0; i < kRecordRegistrationTestRRSetMemberCountMax; ++i )
+	{
+		if( rrSetBitmapExpected & _BitU32( i ) )
+		{
+			me->rdataBase[ 3 ] = (uint8_t) i;
+			FPrintF( stdout, "%s\n\t%s IN A %.4a", separator, me->rrSetName, me->rdataBase );
+			separator = ",";
+		}
+	}
+	FPrintF( stdout, "\n}\n" );
+	OSStatus err;
+	if( me->rrSetBitmapObserved != rrSetBitmapExpected )
+	{
+		FPrintF( stdout, "%{du:time} x Actual RRSet = {", NULL );
+		separator = "";
+		for( unsigned int i = 0; i < kRecordRegistrationTestRRSetMemberCountMax; ++i )
+		{
+			if( me->rrSetBitmapObserved & _BitU32( i ) )
+			{
+				me->rdataBase[ 3 ] = (uint8_t) i;
+				FPrintF( stdout, "%s\n\t%s IN A %.4a", separator, me->rrSetName, me->rdataBase );
+				separator = ",";
+			}
+		}
+		FPrintF( stdout, "\n}\n" );
+		err = kMismatchErr;
+	}
+	else
+	{
+		FPrintF( stdout, "%{du:time} ✓ QueryRecord RRSet matches\n", NULL );
+		++me->rrSetBitmapIndex;
+		const RegisterRecordSubtest * const subtest = _RecordRegistrationTestGetCurrentSubtest( me );
+		if( me->rrSetBitmapIndex < subtest->rrSetBitmapCount )
+		{
+			err = _RecordRegistrationTestActualizeRRSet( me );
+		}
+		else
+		{
+			FPrintF( stdout, "\n" );
+			++me->interfaceSetIndex;
+			if( me->interfaceSetIndex < subtest->interfaceSetCount )
+			{
+				err = _RecordRegistrationTestStartRegistrationsAndQuery( me );
+			}
+			else
+			{
+				++me->subtestIndex;
+				err = _RecordRegistrationTestSubtestStart( me );
+			}
+		}
+	}
+	return( err );
+}
+
+//===========================================================================================================================
+
+static OSStatus	_RecordRegistrationTestActualizeRRSet( const RecordRegistrationTestRef me )
+{
+	FPrintF( stdout, "%{du:time} âš’ Making changes to RRSet\n", NULL );
+	OSStatus err;
+	const uint32_t rrSetBitmap = _RecordRegistrationTestGetCurrentRRSetBitmap( me );
+	for( unsigned int i = 0; i < kRecordRegistrationTestRRSetMemberCountMax; ++i )
+	{
+		const uint32_t bitmask = _BitU32( i );
+		const Boolean needRRSetMember = ( rrSetBitmap & bitmask ) != 0;
+		if( needRRSetMember )
+		{
+			if( !me->recordRefs[ i ] )
+			{
+				err = _RecordRegistrationTestRegisterRecord( me, (uint8_t) i, &me->recordRefs[ i ] );
+				require_noerr( err, exit );
+			}
+		}
+		else
+		{
+			if( me->recordRefs[ i ] )
+			{
+				me->rdataBase[ 3 ] = (uint8_t) i;
+				FPrintF( stdout, "%{du:time} - Deregistering record: %s IN A %.4a\n", NULL, me->rrSetName, me->rdataBase );
+				err = DNSServiceRemoveRecord( me->connection, me->recordRefs[ i ], 0 );
+				require_noerr( err, exit );
+				
+				me->recordRefs[ i ] = NULL;
+			}
+		}
+	}
+	check( !me->timer );
+	me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue );
+	require_action_quiet( me->timer, exit, err = kNoResourcesErr );
+	
+	unsigned int timeLimitMs;
+	const RecordRegistrationSubtestType subtestType = _RecordRegistrationTestGetCurrentSubtestType( me );
+	switch( subtestType )
+	{
+		case kRecordRegistrationSubtestType_ChangingRRSet:
+			timeLimitMs = me->rrSetChangeIntervalMs;
+			break;
+		
+		case kRecordRegistrationSubtestType_Conflict:
+			
+			// Allow for six seconds to pass in case mDNSResponder is currently rate limiting conflicts to one every
+			// five seconds. See rdar://3809484 (Rate limiting imposed too soon) for background on this behavior.
+			
+			timeLimitMs = 6 * kMillisecondsPerSecond;
+			break;
+	}
+	FPrintF( stdout, "%{du:time} â§— Will check QueryRecord results after %u ms\n", NULL, timeLimitMs );
+	dispatch_source_set_timer( me->timer, dispatch_time_milliseconds( timeLimitMs ), DISPATCH_TIME_FOREVER, 0 );
+	dispatch_source_set_event_handler( me->timer,
+	^{
+		dispatch_source_forget( &me->timer );
+		const OSStatus timeoutErr = _RecordRegistrationTestHandleTimeout( me );
+		if( timeoutErr ) _RecordRegistrationTestStop( me, timeoutErr );
+	} );
+	dispatch_activate( me->timer );
+	err = kNoErr;
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+
+static void	_RecordRegistrationTestStart( void * const inCtx )
+{
+	const RecordRegistrationTestRef me = (RecordRegistrationTestRef) inCtx;
+	const OSStatus err = _RecordRegistrationTestSubtestStart( me );
+	require_noerr( err, exit );
+	
+exit:
+	if( err ) _RecordRegistrationTestStop( me, err );
+}
+
+//===========================================================================================================================
+
+static OSStatus	_RecordRegistrationTestRun( const RecordRegistrationTestRef me )
+{
+	dispatch_async_f( me->queue, me, _RecordRegistrationTestStart );
+	dispatch_semaphore_wait( me->doneSem, DISPATCH_TIME_FOREVER );
+	return( me->error );
+}
+
+//===========================================================================================================================
+
+static void	RecordRegistrationTestCommand( void )
+{
+	RecordRegistrationTestRef test = NULL;
+	OSStatus err = CheckIntegerArgument( gRecordRegistrationTest_RRSetChangeIntervalMs, "interval", 0, INT_MAX );
+	require_noerr_quiet( err, exit );
+	
+	unsigned int rrSetChangeIntervalMs = (unsigned int) gRecordRegistrationTest_RRSetChangeIntervalMs;
+	if( rrSetChangeIntervalMs == 0 ) rrSetChangeIntervalMs = 1500;
+	test = _RecordRegistrationTestCreate( rrSetChangeIntervalMs, &err );
+	require_noerr( err, exit );
+	
+	err = _RecordRegistrationTestRun( test );
+	require_noerr( err, exit );
+	
+exit:
+	if( test ) _RecordRegistrationTestRelease( test );
+    gExitCode = err ? 1 : 0;
+}
+
+//===========================================================================================================================
+//	RecordCacheFlushTestCommand
+//===========================================================================================================================
+
+typedef struct RecordCacheFlushTest *		RecordCacheFlushTestRef;
+struct RecordCacheFlushTest
+{
+	dispatch_queue_t			queue;				// Serial queue for test events.
+	dispatch_semaphore_t		doneSem;			// Semaphore to signal when the test is done.
+	DNSServiceRef				queryA;				// Reference for DNSServiceQueryRecord() operation for A record.
+	DNSServiceRef				queryAAAA;			// Reference for DNSServiceQueryRecord() operation for AAAA record.
+	dispatch_source_t			timer;				// Timer for enforcing time limits.
+	char *						recordName;			// Name of records to put in record cache and then flush.
+	MDNSColliderRef				colliderA;			// Collider for sending an A record to put in record cache.
+	MDNSColliderRef				colliderAAAA;		// Collider for sending a AAAA record to put in record cache.
+	mrc_record_cache_flush_t	flush;				// Record cache flush object to flush records.
+	int32_t						refCount;			// Test's reference count.
+	OSStatus					error;				// Test's error code.
+	uint8_t						rdataAAAA[ 16 ];	// The randomly-generated AAAA record RDATA base for the RRSet.
+	uint8_t						rdataA[ 4 ];		// The randomly-generated A record RDATA base for the RRSet.
+	Boolean						haveRecordA;		// True if the A record has an outstanding Add result.
+	Boolean						haveRecordAAAA;		// True if the AAAA record has an outstanding Add result.
+};
+
+//===========================================================================================================================
+
+static void	_RecordCacheFlushTestRetain( const RecordCacheFlushTestRef me )
+{
+	atomic_add_32( &me->refCount, 1 );
+}
+
+//===========================================================================================================================
+
+static void	_RecordCacheFlushTestRelease( const RecordCacheFlushTestRef me )
+{
+	if( atomic_add_and_fetch_32( &me->refCount, -1 ) == 0 )
+	{
+		dispatch_forget( &me->queue );
+		dispatch_forget( &me->doneSem );
+		check( !me->queryA );
+		check( !me->queryAAAA );
+		check( !me->timer );
+		check( !me->recordName );
+		check( !me->colliderA );
+		check( !me->colliderAAAA );
+		check( !me->flush );
+		free( me );
+	}
+}
+
+//===========================================================================================================================
+
+static RecordCacheFlushTestRef	_RecordCacheFlushTestCreate( OSStatus * const outError )
+{
+	OSStatus err;
+	RecordCacheFlushTestRef test = NULL;
+	RecordCacheFlushTestRef obj = (RecordCacheFlushTestRef) calloc( 1, sizeof( *obj ) );
+	require_action( obj, exit, err = kNoMemoryErr );
+	
+	obj->refCount	= 1;
+	obj->error		= kInProgressErr;
+	obj->queue = dispatch_queue_create( "com.apple.dnssdutil.record-cache-flush-test", DISPATCH_QUEUE_SERIAL );
+	require_action( obj->queue, exit, err = kNoResourcesErr );
+	
+	obj->doneSem = dispatch_semaphore_create( 0 );
+	require_action( obj->doneSem, exit, err = kNoResourcesErr );
+	
+	test = obj;
+	obj = NULL;
+	err = kNoErr;
+	
+exit:
+	if( outError ) *outError = err;
+	if( obj ) _RecordCacheFlushTestRelease( obj );
+	return( test );
+}
+
+//===========================================================================================================================
+
+static void	_RecordCacheFlushTestStop( const RecordCacheFlushTestRef me, const OSStatus inError )
+{
+	me->error = inError;
+	if( !me->error )
+	{
+		FPrintF( stdout, "%{du:time} Test PASSED\n", NULL );
+	}
+	else
+	{
+		FPrintF( stdout, "%{du:time} Test FAILED: %#m\n", NULL, me->error );
+	}
+	DNSServiceForget( &me->queryA );
+	DNSServiceForget( &me->queryAAAA );
+	dispatch_source_forget( &me->timer );
+	ForgetMem( &me->recordName );
+	MDNSColliderForget( &me->colliderA );
+	MDNSColliderForget( &me->colliderAAAA );
+	mrc_record_cache_flush_forget( &me->flush );
+	dispatch_semaphore_signal( me->doneSem );
+}
+
+//===========================================================================================================================
+
+static void DNSSD_API
+	_RecordCacheFlushTestQueryRecordCallback(
+		__unused const DNSServiceRef	inSDRef,
+		const DNSServiceFlags			inFlags,
+		__unused const uint32_t			inIfIndex,
+		const DNSServiceErrorType		inError,
+		const char * const				inFullName,
+		const uint16_t					inType,
+		const uint16_t					inClass,
+		const uint16_t					inRDataLen,
+		const void * const				inRDataPtr,
+		__unused const uint32_t			inTTL,
+		void * const					inCtx )
+{
+	OSStatus err;
+	const RecordCacheFlushTestRef me = (RecordCacheFlushTestRef) inCtx;
+	
+	// Print result.
+	
+	char ifNameBuf[ kInterfaceNameBufLen ];
+	const char * const ifName = InterfaceIndexToName( inIfIndex, ifNameBuf );
+	char typeBuf[ 32 ];
+	const char *typeStr = DNSRecordTypeValueToString( inType );
+	if( !typeStr )
+	{
+		SNPrintF( typeBuf, sizeof( typeBuf ), "TYPE%u", inType );
+		typeStr = typeBuf;
+	}
+	char classBuf[ 32 ];
+	const char *classStr;
+	if( inClass == kDNSServiceClass_IN )
+	{
+		classStr = "IN";
+	}
+	else
+	{
+		SNPrintF( classBuf, sizeof( classBuf ), "CLASS%u", inClass );
+		classStr = classBuf;
+	}
+	char *rdataStr = NULL;
+	DNSRecordDataToString( inRDataPtr, inRDataLen, inType, &rdataStr );
+	if( !rdataStr )
+	{
+		ASPrintF( &rdataStr, "%#H", inRDataPtr, (int) inRDataLen, (int) inRDataLen );
+		require_action( rdataStr, exit, err = kNoMemoryErr );
+	}
+	FPrintF( stdout, "%{du:time} → QueryRecord result -- flags: 0x%0X (%s), interface: %s/%d, error: %#m, name: %s,"
+		" type: %s, class: %s, ttl: %u, rdata: %s\n",
+		NULL, inFlags, (inFlags & kDNSServiceFlagsAdd) ? "Add" : "Rmv", ifName ? ifName : "", (int32_t) inIfIndex, inError,
+		inFullName, typeStr, classStr, inTTL, rdataStr );
+	
+	// Check callback arguments.
+	
+	uint8_t fullName[ kDomainNameLengthMax ];
+	err = DomainNameFromString( fullName, inFullName, NULL );
+	require_noerr_quiet( err, exit );
+	
+	uint8_t recordName[ kDomainNameLengthMax ];
+	err = DomainNameFromString( recordName, me->recordName, NULL );
+	require_noerr_quiet( err, exit );
+	
+	const Boolean nameMatch = DomainNameEqual( fullName, recordName );
+	require_action( nameMatch, exit, err = kNameErr );
+	require_action( inClass == kDNSServiceClass_IN, exit, err = kTypeErr );
+	
+	switch( inType )
+	{
+		case kDNSServiceType_A:
+		{
+			require_action( inRDataLen == 4, exit, err = kSizeErr );
+			
+			const int cmp = memcmp( inRDataPtr, me->rdataA, sizeof( me->rdataA ) );
+			require_action( cmp == 0, exit, err = kValueErr );
+			break;
+		}
+		case kDNSServiceType_AAAA:
+		{
+			require_action( inRDataLen == 16, exit, err = kSizeErr );
+			
+			const int cmp = memcmp( inRDataPtr, me->rdataAAAA, sizeof( me->rdataAAAA ) );
+			require_action( cmp == 0, exit, err = kValueErr );
+			break;
+		}
+		default:
+			err = kTypeErr;
+			goto exit;
+	}
+	err = inError;
+	require_noerr( err, exit );
+	
+	if( inFlags & kDNSServiceFlagsAdd )
+	{
+		// Add events are only expected before flushing the records from the record cache. After the flush, there should
+		// be no Add events.
+		
+		require_action( !me->flush, exit, err = kUnexpectedErr );
+		
+		// Make sure we get exactly one Add for the A record and exactly one Add for the AAAA record.
+		
+		switch( inType )
+		{
+			case kDNSServiceType_A:
+				require_action( !me->haveRecordA, exit, err = kDuplicateErr );
+				
+				me->haveRecordA = true;
+				break;
+			
+			case kDNSServiceType_AAAA:
+				require_action( !me->haveRecordAAAA, exit, err = kDuplicateErr );
+				
+				me->haveRecordAAAA = true;
+				break;
+		}
+	}
+	else
+	{
+		// Rmv events are only expected after flushing the records from the record cache. Before the flush, there should
+		// be no Rmv events.
+		
+		require_action( me->flush, exit, err = kUnexpectedErr );
+		
+		// Make sure we get exactly one Rmv for the A record and exactly one Rmv for the AAAA record. Also, there should
+		// be no Rmv event unless there was a corresponding Add event beforehand.
+		
+		switch( inType )
+		{
+			case kDNSServiceType_A:
+				require_action( me->haveRecordA, exit, err = kUnexpectedErr );
+				
+				me->haveRecordA = false;
+				break;
+			
+			case kDNSServiceType_AAAA:
+				require_action( me->haveRecordAAAA, exit, err = kUnexpectedErr );
+				
+				me->haveRecordAAAA = false;
+				break;
+		}
+	}
+	
+exit:
+	ForgetMem( &rdataStr );
+	if( err ) _RecordCacheFlushTestStop( me, err );
+}
+
+//===========================================================================================================================
+
+static OSStatus	_RecordCacheFlushTestHandlePostFlushTimeout( const RecordCacheFlushTestRef me )
+{
+	OSStatus err;
+	if( !me->haveRecordA && !me->haveRecordAAAA )
+	{
+		FPrintF( stdout, "%{du:time} ✓ Got expected QueryRecord Rmv results\n", NULL );
+		err = kNoErr;
+	}
+	else
+	{
+		if( me->haveRecordA )
+		{
+			FPrintF( stdout, "%{du:time} x Didn't get Rmv for A record", NULL );
+		}
+		if( me->haveRecordAAAA )
+		{
+			FPrintF( stdout, "%{du:time} x Didn't get Rmv for AAAA record", NULL );
+		}
+		err = kUnexpectedErr;
+	}
+	return( err );
+}
+
+//===========================================================================================================================
+
+static OSStatus	_RecordCacheFlushTestHandlePreFlushTimeout( const RecordCacheFlushTestRef me )
+{
+	OSStatus err;
+	mdns_domain_name_t recordName = NULL;
+	if( me->haveRecordA && me->haveRecordAAAA )
+	{
+		FPrintF( stdout, "%{du:time} ✓ Got expected QueryRecord Add results\n", NULL );
+		
+		// Start record cache flush.
+		
+		FPrintF( stdout, "%{du:time} âš’ Flushing %s records from record cache\n", NULL, me->recordName );
+		recordName = mdns_domain_name_create( me->recordName, 0, &err );
+		require_noerr( err, exit );
+		
+		check( !me->flush );
+		me->flush = mrc_record_cache_flush_create();
+		require_action( me->flush, exit, err = kNoResourcesErr );
+		
+		mrc_record_cache_flush_set_queue( me->flush, me->queue );
+		mrc_record_cache_flush_set_record_name( me->flush, recordName );
+		_RecordCacheFlushTestRetain( me );
+		mrc_record_cache_flush_set_result_handler( me->flush,
+		^( const mrc_record_cache_flush_result_t inResult, const OSStatus inError )
+		{
+			if( me->flush )
+			{
+				switch( inResult )
+				{
+					case mrc_record_cache_flush_result_complete:
+						FPrintF( stdout, "%{du:time} ✓ Record cache flush result: complete\n", NULL );
+						break;
+					
+					case mrc_record_cache_flush_result_incomplete:
+						FPrintF( stdout, "%{du:time} x Record cache flush result: incomplete", NULL );
+						if( inError )
+						{
+							FPrintF( stdout, " (error: %#m)", inError );
+						}
+						FPrintF( stdout, "\n" );
+						break;
+				}
+				if( inError ) _RecordCacheFlushTestStop( me, inError );
+			}
+			_RecordCacheFlushTestRelease( me );
+		} );
+		mrc_record_cache_flush_activate( me->flush );
+		
+		// Start timer.
+		
+		check( !me->timer );
+		me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue );
+		require_action_quiet( me->timer, exit, err = kNoResourcesErr );
+		
+		const unsigned int timeLimitMs = 3 * kMillisecondsPerSecond;
+		FPrintF( stdout, "%{du:time} â§— Will check QueryRecord results after %u ms\n", NULL, timeLimitMs );
+		dispatch_source_set_timer( me->timer, dispatch_time_milliseconds( timeLimitMs ), DISPATCH_TIME_FOREVER, 0 );
+		dispatch_source_set_event_handler( me->timer,
+		^{
+			dispatch_source_forget( &me->timer );
+			const OSStatus timeoutErr = _RecordCacheFlushTestHandlePostFlushTimeout( me );
+			_RecordCacheFlushTestStop( me, timeoutErr );
+		} );
+		dispatch_activate( me->timer );
+	}
+	else
+	{
+		if( !me->haveRecordA )
+		{
+			FPrintF( stdout, "%{du:time} x Didn't get Add for A record", NULL );
+		}
+		if( !me->haveRecordAAAA )
+		{
+			FPrintF( stdout, "%{du:time} x Didn't get Add for AAAA record", NULL );
+		}
+		err = kUnexpectedErr;
+	}
+	
+exit:
+	mdns_forget( &recordName );
+	return( err );
+}
+
+//===========================================================================================================================
+
+static void	_RecordCacheFlushTestStart( const RecordCacheFlushTestRef me )
+{
+	// Create a randomly-generated record name.
+	
+	OSStatus err;
+	char tag[ 6 + 1 ];
+	_RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag );
+	check( !me->recordName );
+	ASPrintF( &me->recordName, "record-cache-flush-test-%s.local", tag );
+	require_action( me->recordName, exit, err = kNoMemoryErr );
+	
+	// Query for A record.
+	
+	FPrintF( stdout, "%{du:time} ☀ Starting QueryRecord for %s A record\n", NULL, me->recordName );
+	check( !me->queryA );
+	const uint32_t loopbackIndex = _RecordRegistrationTestGetLoopbackInterfaceIndex();
+	err = DNSServiceQueryRecord( &me->queryA, 0, loopbackIndex, me->recordName, kDNSServiceType_A, kDNSServiceClass_IN,
+		_RecordCacheFlushTestQueryRecordCallback, me );
+	require_noerr( err, exit );
+	
+	err = DNSServiceSetDispatchQueue( me->queryA, me->queue );
+	require_noerr( err, exit );
+	
+	// Query for AAAA record.
+	
+	FPrintF( stdout, "%{du:time} ☀ Starting QueryRecord for %s AAAA record\n", NULL, me->recordName );
+	check( !me->queryAAAA );
+	err = DNSServiceQueryRecord( &me->queryAAAA, 0, loopbackIndex, me->recordName, kDNSServiceType_AAAA, kDNSServiceClass_IN,
+		_RecordCacheFlushTestQueryRecordCallback, me );
+	require_noerr( err, exit );
+	
+	err = DNSServiceSetDispatchQueue( me->queryAAAA, me->queue );
+	require_noerr( err, exit );
+	
+	// Generate random A and AAAA record RDATA.
+	
+	RandomBytes( me->rdataA, sizeof( me->rdataA ) );
+	RandomBytes( me->rdataAAAA, sizeof( me->rdataAAAA ) );
+	
+	// Create collider to send A record.
+	
+	uint8_t recordName[ kDomainNameLengthMax ];
+	err = DomainNameFromString( recordName, me->recordName, NULL );
+	require_noerr( err, exit );
+	
+	MDNSColliderRef collider = NULL;
+	FPrintF( stdout, "%{du:time} ✈ Sending mDNS message containing record: %s IN A %.4a\n",
+		NULL, me->recordName, me->rdataA );
+	err = MDNSColliderCreate( me->queue, &collider );
+	require_noerr( err, exit );
+	
+	MDNSColliderSetProtocols( collider, kMDNSColliderProtocol_IPv4 );
+	MDNSColliderSetInterfaceIndex( collider, loopbackIndex );
+	err = MDNSColliderSetRecord( collider, recordName, kDNSServiceType_A, me->rdataA, sizeof( me->rdataA ) );
+	require_noerr( err, exit );
+	err = MDNSColliderSetProgram( collider, "send" );
+	require_noerr( err, exit );
+	
+	err = MDNSColliderStart( collider );
+	require_noerr( err, exit );
+	
+	check( !me->colliderA );
+	me->colliderA = collider;
+	collider = NULL;
+	
+	// Create collider to send AAAA record.
+	
+	FPrintF( stdout, "%{du:time} ✈ Sending mDNS message containing record: %s IN AAAA %.16a\n",
+		NULL, me->recordName, me->rdataAAAA );
+	err = MDNSColliderCreate( me->queue, &collider );
+	require_noerr( err, exit );
+	
+	MDNSColliderSetProtocols( collider, kMDNSColliderProtocol_IPv4 );
+	MDNSColliderSetInterfaceIndex( collider, loopbackIndex );
+	err = MDNSColliderSetRecord( collider, recordName, kDNSServiceType_AAAA, me->rdataAAAA, sizeof( me->rdataAAAA ) );
+	require_noerr( err, exit );
+	
+	err = MDNSColliderSetProgram( collider, "send" );
+	require_noerr( err, exit );
+	
+	err = MDNSColliderStart( collider );
+	require_noerr( err, exit );
+	
+	check( !me->colliderAAAA );
+	me->colliderAAAA = collider;
+	collider = NULL;
+	
+	// Start timer.
+	
+	check( !me->timer );
+	me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue );
+	require_action_quiet( me->timer, exit, err = kNoResourcesErr );
+	
+	const unsigned int timeLimitMs = 3 * kMillisecondsPerSecond;
+	FPrintF( stdout, "%{du:time} â§— Will check QueryRecord results after %u ms\n", NULL, timeLimitMs );
+	dispatch_source_set_timer( me->timer, dispatch_time_milliseconds( timeLimitMs ), DISPATCH_TIME_FOREVER, 0 );
+	dispatch_source_set_event_handler( me->timer,
+	^{
+		dispatch_source_forget( &me->timer );
+		const OSStatus timeoutErr = _RecordCacheFlushTestHandlePreFlushTimeout( me );
+		if( timeoutErr ) _RecordCacheFlushTestStop( me, timeoutErr );
+	} );
+	dispatch_activate( me->timer );
+	
+exit:
+	if( err ) _RecordCacheFlushTestStop( me, err );
+}
+
+//===========================================================================================================================
+
+static OSStatus	_RecordCacheFlushTestRun( const RecordCacheFlushTestRef me )
+{
+	dispatch_async( me->queue,
+	^{
+		_RecordCacheFlushTestStart( me );
+	} );
+	dispatch_semaphore_wait( me->doneSem, DISPATCH_TIME_FOREVER );
+	return( me->error );
+}
+
+//===========================================================================================================================
+
+static void	RecordCacheFlushTestCommand( void )
+{
+	OSStatus err;
+	RecordCacheFlushTestRef test = _RecordCacheFlushTestCreate( &err );
+	require_noerr( err, exit );
+	
+	err = _RecordCacheFlushTestRun( test );
+	require_noerr( err, exit );
+	
+exit:
+	if( test ) _RecordCacheFlushTestRelease( test );
+    gExitCode = err ? 1 : 0;
+}
+
+//===========================================================================================================================
+//	ResolverOverrideTestCommand
+//===========================================================================================================================
+
+// Each subtest uses DNSServiceQueryRecordWithAttribute() to query for an A record that can only be resolved via a DNS
+// server that is launched by the test itself. The DNS server is configured to only provide responses for domain names
+// in a randomized domain such as resolver-override-test-00zn9v.test, and is only capable of receiving DNS queries on
+// the loopback interface at a randomly-selected port.
+//
+// A libnetwork resolver configuration that describes the test's DNS service is published, but it uses a bogus
+// randomized domain such as resolver-override-test-cgm5xp.invalid, to preclude the possibility of mDNSResponder using
+// domain matching to assign the test's DNS service to the test's DNS queries.
+//
+// The test uses DNSServiceAttributeSetResolverOverride() to set the libnetwork resolver configuration's UUID in the
+// attribute passed to DNSServiceQueryRecordWithAttribute(). This should cause the associated DNS queries to be sent to
+// the test's DNS service, which is the only way to get the correct RDATA. Some subtests don't use the resolver override
+// attribute at all or use the resolver attribute with the incorrect resolver configuration UUID to verify that the
+// correct RDATA cannot be obtained unless the correct resolver UUID is used.
+//
+// Subtests are performed with and without the kDNSServiceFlagsPathEvaluationDone flag to make sure that the expected
+// outcome occurs regardless of whether or not path evaluation is performed. Path evaluation sometimes influences DNS
+// service selection, but it should have no effect when a resolver override is used.
+
+typedef enum
+{
+	kResolverOverrideAction_UseCorrectUUID		= 1,
+	kResolverOverrideAction_UseIncorrectUUID	= 2,
+	kResolverOverrideAction_DoNotUse			= 3,
+	
+}	ResolverOverrideAction;
+
+typedef struct
+{
+	DNSServiceFlags			flags;
+	ResolverOverrideAction	resolverOverrideAction;
+	Boolean					queryRecordsWithCNAMEs;
+	
+}	ResolverOverrideSubtest;
+
+static const ResolverOverrideSubtest		kResolverOverrideSubtests[] =
+{
+	{
+		.resolverOverrideAction	= kResolverOverrideAction_DoNotUse,
+		.flags					= 0,
+		.queryRecordsWithCNAMEs	= false,
+	},
+	{
+		.resolverOverrideAction	= kResolverOverrideAction_DoNotUse,
+		.flags					= kDNSServiceFlagsPathEvaluationDone,
+		.queryRecordsWithCNAMEs	= false,
+	},
+	{
+		.resolverOverrideAction	= kResolverOverrideAction_UseCorrectUUID,
+		.flags					= 0,
+		.queryRecordsWithCNAMEs	= false,
+	},
+	{
+		.resolverOverrideAction	= kResolverOverrideAction_UseCorrectUUID,
+		.flags					= kDNSServiceFlagsPathEvaluationDone,
+		.queryRecordsWithCNAMEs	= false,
+	},
+	{
+		.resolverOverrideAction	= kResolverOverrideAction_UseCorrectUUID,
+		.flags					= 0,
+		.queryRecordsWithCNAMEs	= true,
+	},
+	{
+		.resolverOverrideAction	= kResolverOverrideAction_UseCorrectUUID,
+		.flags					= kDNSServiceFlagsPathEvaluationDone,
+		.queryRecordsWithCNAMEs	= true,
+	},
+	{
+		.resolverOverrideAction	= kResolverOverrideAction_UseIncorrectUUID,
+		.flags					= 0,
+		.queryRecordsWithCNAMEs	= false,
+	},
+	{
+		.resolverOverrideAction	= kResolverOverrideAction_UseIncorrectUUID,
+		.flags					= kDNSServiceFlagsPathEvaluationDone,
+		.queryRecordsWithCNAMEs	= false,
+	},
+};
+
+typedef struct ResolverOverrideTest *		ResolverOverrideTestRef;
+struct ResolverOverrideTest
+{
+	dispatch_queue_t			queue;					// Serial queue for test events.
+	dispatch_semaphore_t		doneSem;				// Semaphore to signal when the test is done.
+	DNSServerRef				server;					// Reference to DNS server.
+	DNSServiceAttributeRef		resolverOverrideAttr;	// Attribute for the resolver override.
+	nw_resolver_config_t		resolverConfig;			// Resolver configuration for DNS service.
+	DNSServiceRef				query;					// Reference for the QueryRecord operation to query for A records.
+	dispatch_source_t			timer;					// Timer for enforcing time limits.
+	size_t						subtestIndex;			// Index of current subtest.
+	char *						recordName;				// Name of records to put in record cache and then flush.
+	char *						domain;					// DNS service's domain.
+	sockaddr_ip					serverAddr;				// DNS server address.
+	int32_t						refCount;				// Test's reference count.
+	OSStatus					error;					// Test's error code.
+	uuid_t						resolverUUID;			// Resolver configuration's UUID.
+	uint8_t						expectedRData[ 4 ];		// Expected A record RDATA in the result of the current QueryRecord.
+	Boolean						gotRData;				// True if we got a QueryRecord Add result for the record name.
+	Boolean						stopped;				// True if the test has been stopped.
+};
+
+//===========================================================================================================================
+
+static void	_ResolverOverrideTestRelease( const ResolverOverrideTestRef me )
+{
+	if( atomic_add_and_fetch_32( &me->refCount, -1 ) == 0 )
+	{
+		dispatch_forget( &me->queue );
+		dispatch_forget( &me->doneSem );
+		check( !me->server );
+		check( !me->resolverConfig );
+		check( !me->resolverOverrideAttr );
+		check( !me->query );
+		check( !me->timer );
+		check( !me->recordName );
+		check( !me->domain );
+		free( me );
+	}
+}
+
+//===========================================================================================================================
+
+static ResolverOverrideTestRef	_ResolverOverrideTestCreate( OSStatus * const outError )
+{
+	OSStatus err;
+	ResolverOverrideTestRef test = NULL;
+	ResolverOverrideTestRef obj = (ResolverOverrideTestRef) calloc( 1, sizeof( *obj ) );
+	require_action( obj, exit, err = kNoMemoryErr );
+	
+	obj->refCount	= 1;
+	obj->error		= kInProgressErr;
+	obj->queue = dispatch_queue_create( "com.apple.dnssdutil.resolver-override-test", DISPATCH_QUEUE_SERIAL );
+	require_action( obj->queue, exit, err = kNoResourcesErr );
+	
+	obj->doneSem = dispatch_semaphore_create( 0 );
+	require_action( obj->doneSem, exit, err = kNoResourcesErr );
+	
+	test = obj;
+	obj = NULL;
+	err = kNoErr;
+	
+exit:
+	if( outError ) *outError = err;
+	if( obj ) _ResolverOverrideTestRelease( obj );
+	return( test );
+}
+
+//===========================================================================================================================
+
+static void	_ResolverOverrideTestStop( const ResolverOverrideTestRef me, const OSStatus inError )
+{
+	require_return( !me->stopped );
+	
+	me->stopped = true;
+	me->error = inError;
+	if( me->resolverConfig )
+	{
+		uuid_t uuid;
+		uuid_string_t uuidStr;
+		nw_resolver_config_get_identifier( me->resolverConfig, uuid );
+		uuid_unparse_upper( uuid, uuidStr );
+		FPrintF( stdout, "\n%{du:time} âš’ Unpublishing resolver configuration (%s): %@\n",
+			NULL, uuidStr, me->resolverConfig );
+		nw_resolver_config_unpublish( me->resolverConfig );
+		nw_forget( &me->resolverConfig );
+	}
+	FPrintF( stdout, "\n" );
+	if( !me->error )
+	{
+		FPrintF( stdout, "%{du:time} Test PASSED\n", NULL );
+	}
+	else
+	{
+		FPrintF( stdout, "%{du:time} Test FAILED: %#m\n", NULL, me->error );
+	}
+	if( me->server )
+	{
+		_DNSServerStop( me->server );
+		CFForget( &me->server );
+	}
+	_DNSServiceAttrForget( &me->resolverOverrideAttr );
+	DNSServiceForget( &me->query );
+	dispatch_source_forget( &me->timer );
+	ForgetMem( &me->recordName );
+	ForgetMem( &me->domain );
+	dispatch_semaphore_signal( me->doneSem );
+}
+
+//===========================================================================================================================
+
+static const ResolverOverrideSubtest *	_ResolverOverrideTestGetCurrentSubtest( const ResolverOverrideTestRef me )
+{
+	require_fatal( me->subtestIndex < countof( kResolverOverrideSubtests ),
+		"Invalid subtest index %zu >= %zu", me->subtestIndex, countof( kResolverOverrideSubtests ) );
+	return( &kResolverOverrideSubtests[ me->subtestIndex ] );
+}
+
+//===========================================================================================================================
+
+static void DNSSD_API
+	_ResolverOverrideTestQueryRecordCallback(
+		__unused const DNSServiceRef	inSDRef,
+		const DNSServiceFlags			inFlags,
+		__unused const uint32_t			inIfIndex,
+		const DNSServiceErrorType		inError,
+		const char * const				inFullName,
+		const uint16_t					inType,
+		const uint16_t					inClass,
+		const uint16_t					inRDataLen,
+		const void * const				inRDataPtr,
+		__unused const uint32_t			inTTL,
+		void * const					inCtx )
+{
+	OSStatus err;
+	const ResolverOverrideTestRef me = (ResolverOverrideTestRef) inCtx;
+	
+	// Print result.
+	
+	char ifNameBuf[ kInterfaceNameBufLen ];
+	const char * const ifName = InterfaceIndexToName( inIfIndex, ifNameBuf );
+	char typeBuf[ 32 ];
+	const char *typeStr = DNSRecordTypeValueToString( inType );
+	if( !typeStr )
+	{
+		SNPrintF( typeBuf, sizeof( typeBuf ), "TYPE%u", inType );
+		typeStr = typeBuf;
+	}
+	char classBuf[ 32 ];
+	const char *classStr;
+	if( inClass == kDNSServiceClass_IN )
+	{
+		classStr = "IN";
+	}
+	else
+	{
+		SNPrintF( classBuf, sizeof( classBuf ), "CLASS%u", inClass );
+		classStr = classBuf;
+	}
+	char *rdataStr = NULL;
+	DNSRecordDataToString( inRDataPtr, inRDataLen, inType, &rdataStr );
+	if( !rdataStr )
+	{
+		ASPrintF( &rdataStr, "%#H", inRDataPtr, (int) inRDataLen, (int) inRDataLen );
+		require_action( rdataStr, exit, err = kNoMemoryErr );
+	}
+	FPrintF( stdout, "%{du:time} → QueryRecord result -- flags: 0x%0X (%s), interface: %s/%d, error: %#m, name: %s,"
+		" type: %s, class: %s, ttl: %u, rdata: %s\n",
+		NULL, inFlags, (inFlags & kDNSServiceFlagsAdd) ? "Add" : "Rmv", ifName ? ifName : "", (int32_t) inIfIndex, inError,
+		inFullName, typeStr, classStr, inTTL, rdataStr );
+	
+	// Check callback arguments.
+	
+	uint8_t fullName[ kDomainNameLengthMax ];
+	err = DomainNameFromString( fullName, inFullName, NULL );
+	require_noerr_quiet( err, exit );
+	
+	uint8_t queriedRecordName[ kDomainNameLengthMax ];
+	err = DomainNameFromString( queriedRecordName, me->recordName, NULL );
+	require_noerr_quiet( err, exit );
+	
+	const uint8_t *expectedRecordName;
+	const ResolverOverrideSubtest * const subtest = _ResolverOverrideTestGetCurrentSubtest( me );
+	if( subtest->queryRecordsWithCNAMEs )
+	{
+		expectedRecordName = DomainNameGetNextLabel( queriedRecordName ); // Skip alias label to get to the canonical name.
+	}
+	else
+	{
+		expectedRecordName = queriedRecordName;
+	}
+	const Boolean nameMatch = DomainNameEqual( fullName, expectedRecordName );
+	require_action( nameMatch, exit, err = kNameErr );
+	require_action( inClass == kDNSServiceClass_IN, exit, err = kTypeErr );
+	
+	switch( inType )
+	{
+		case kDNSServiceType_A:
+		{
+			require_action( inRDataLen == sizeof( me->expectedRData ), exit, err = kSizeErr );
+			
+			const int cmp = memcmp( inRDataPtr, me->expectedRData, sizeof( me->expectedRData ) );
+			require_action( cmp == 0, exit, err = kValueErr );
+			break;
+		}
+		default:
+			err = kTypeErr;
+			goto exit;
+	}
+	err = inError;
+	require_noerr( err, exit );
+	require_action( inFlags & kDNSServiceFlagsAdd, exit, err = kUnexpectedErr );
+	require_action( !me->gotRData, exit, err = kDuplicateErr );
+	
+	me->gotRData = true;
+	
+exit:
+	ForgetMem( &rdataStr );
+	if( err ) _ResolverOverrideTestStop( me, err );
+}
+
+//===========================================================================================================================
+
+static void	_ResolverOverrideTestRetain( const ResolverOverrideTestRef me )
+{
+	atomic_add_32( &me->refCount, 1 );
+}
+
+//===========================================================================================================================
+
+static OSStatus	_ResolverOverrideTestHandleTimeout( ResolverOverrideTestRef inTest );
+
+static OSStatus	_ResolverOverrideTestStartSubtest( const ResolverOverrideTestRef me )
+{
+	OSStatus err;
+	if( me->subtestIndex >= countof( kResolverOverrideSubtests ) )
+	{
+		_ResolverOverrideTestRetain( me );
+		dispatch_async( me->queue,
+		^{
+			_ResolverOverrideTestStop( me, kNoErr );
+			_ResolverOverrideTestRelease( me );
+		} );
+		err = kNoErr;
+		goto exit;
+	}
+	const char *resolverOverrideActionStr;
+	const ResolverOverrideSubtest * const subtest = _ResolverOverrideTestGetCurrentSubtest( me );
+	switch( subtest->resolverOverrideAction )
+	{
+		case kResolverOverrideAction_UseCorrectUUID:
+			resolverOverrideActionStr = "correct UUID";
+			break;
+		
+		case kResolverOverrideAction_UseIncorrectUUID:
+			resolverOverrideActionStr = "incorrect UUID";
+			break;
+		
+		case kResolverOverrideAction_DoNotUse:
+			resolverOverrideActionStr = "not used";
+			break;
+		
+		default:
+			FatalErrorF( "Unhandled resolver overraide action value: %d", subtest->resolverOverrideAction );
+	}
+	FPrintF( stdout, "\n%{du:time} Subtest %zu: Querying for A record -- "
+		"resolver override: %s, flags: %#{flags}, record has CNAMEs: %s\n",
+		NULL, me->subtestIndex + 1, resolverOverrideActionStr, subtest->flags, kDNSServiceFlagsDescriptors,
+		YesNoStr( subtest->queryRecordsWithCNAMEs ) );
+	
+	// Create record name.
+	
+	char tag[ 6 + 1 ];
+	_RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag );
+	const uint8_t offset = (uint8_t)( ( me->subtestIndex + 1 ) * 10 );
+	ForgetMem( &me->recordName );
+	ASPrintF( &me->recordName,
+		"%stag-%s.offset-%u.%s", subtest->queryRecordsWithCNAMEs ? "alias-3." : "", tag, offset, me->domain );
+	require_action( me->recordName, exit, err = kNoMemoryErr );
+	
+	// Create an attribute for the resolver UUID.
+	
+	DNSServiceAttributeRef attr;
+	switch( subtest->resolverOverrideAction )
+	{
+		case kResolverOverrideAction_UseCorrectUUID:
+		case kResolverOverrideAction_UseIncorrectUUID:
+			if( !me->resolverOverrideAttr )
+			{
+				me->resolverOverrideAttr = DNSServiceAttributeCreate();
+				require_action( me->resolverOverrideAttr, exit, err = kNoResourcesErr );
+			}
+			uuid_t randomUUID;
+			uuid_clear( randomUUID );
+			const uuid_t *resolverUUID;
+			if( subtest->resolverOverrideAction == kResolverOverrideAction_UseCorrectUUID )
+			{
+				resolverUUID = &me->resolverUUID;
+			}
+			else
+			{
+				uuid_generate_random( randomUUID );
+				resolverUUID = &randomUUID;
+			}
+			if( __builtin_available( macOS 15.0, iOS 18.0, tvOS 18.0, watchOS 11.0, * ) )
+			{
+				uuid_string_t uuidStr;
+				uuid_unparse_upper( *resolverUUID, uuidStr );
+				FPrintF( stdout, "%{du:time} âš’ Setting resolver override: %s\n", NULL, uuidStr );
+				err = DNSServiceAttributeSetResolverOverride( me->resolverOverrideAttr, *resolverUUID );
+				require_noerr( err, exit );
+			}
+			else
+			{
+				FPrintF( stderr, "DNSServiceAttributeSetResolverOverride() is not available on this OS build." );
+				err = kUnsupportedErr;
+				goto exit;
+			}
+			attr = me->resolverOverrideAttr;
+			break;
+		
+		case kResolverOverrideAction_DoNotUse:
+			attr = NULL;
+			break;
+		
+		default:
+			FatalErrorF( "Unhandled resolver overraide action value: %d", subtest->resolverOverrideAction );
+	}
+	// Query for A record.
+	
+	FPrintF( stdout, "%{du:time} ☀ Starting QueryRecord for %s A record\n", NULL, me->recordName );
+	const uint32_t expectedIPv4Addr = kDNSServerBaseAddrV4 + 1 + offset;
+	WriteBig32Typed( me->expectedRData, expectedIPv4Addr );
+	check( !me->query );
+	err = DNSServiceQueryRecordWithAttribute( &me->query, subtest->flags, kDNSServiceInterfaceIndexAny, me->recordName,
+		kDNSServiceType_A, kDNSServiceClass_IN, attr, _ResolverOverrideTestQueryRecordCallback, me );
+	require_noerr( err, exit );
+	
+	err = DNSServiceSetDispatchQueue( me->query, me->queue );
+	require_noerr( err, exit );
+	
+	// Start timer.
+	
+	check( !me->timer );
+	me->timer = dispatch_source_create( DISPATCH_SOURCE_TYPE_TIMER, 0, 0, me->queue );
+	require_action_quiet( me->timer, exit, err = kNoResourcesErr );
+	
+	const unsigned int timeLimitSecs = 3;
+	FPrintF( stdout, "%{du:time} â§– Will check QueryRecord results after %u seconds\n", NULL, timeLimitSecs );
+	dispatch_source_set_timer( me->timer, dispatch_time_seconds( timeLimitSecs ), DISPATCH_TIME_FOREVER, 0 );
+	dispatch_source_set_event_handler( me->timer,
+	^{
+		const OSStatus timeoutErr = _ResolverOverrideTestHandleTimeout( me );
+		if( timeoutErr ) _ResolverOverrideTestStop( me, timeoutErr );
+	} );
+	dispatch_activate( me->timer );
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+
+static OSStatus	_ResolverOverrideTestHandleTimeout( const ResolverOverrideTestRef me )
+{
+	OSStatus err;
+	DNSServiceForget( &me->query );
+	dispatch_source_forget( &me->timer );
+	const ResolverOverrideSubtest * const subtest = _ResolverOverrideTestGetCurrentSubtest( me );
+	switch( subtest->resolverOverrideAction )
+	{
+		case kResolverOverrideAction_UseCorrectUUID:
+			FPrintF( stdout, "%{du:time} %s correct RDATA: %.4a\n",
+				NULL, me->gotRData ? "✓ Got" : "x Didn't get", me->expectedRData );
+			require_action( me->gotRData, exit, err = kValueErr );
+			break;
+		
+		case kResolverOverrideAction_UseIncorrectUUID:
+		case kResolverOverrideAction_DoNotUse:
+			if( !me->gotRData )
+			{
+				FPrintF( stdout, "%{du:time} ✓ Didn't get RDATA, as expected, since correct resolver override wasn't used\n",
+					NULL );
+			}
+			else
+			{
+				FPrintF( stdout, "%{du:time} x Got correct RDATA despite not using resolver override: %.4a\n",
+					NULL, me->expectedRData );
+			}
+			require_action( !me->gotRData, exit, err = kUnexpectedErr );
+			break;
+		
+		default:
+			FatalErrorF( "Unhandled resolver overraide action value: %d", subtest->resolverOverrideAction );
+	}
+	++me->subtestIndex;
+	me->gotRData = false;
+	err = _ResolverOverrideTestStartSubtest( me );
+	require_noerr( err, exit );
+	
+exit:
+	return( err );
+}
+
+//===========================================================================================================================
+
+static void	_ResolverOverrideTestDNSServerStartHandler( const uint16_t inDNSServerPort, void * const inCtx )
+{
+	OSStatus err;
+	char *bogusDomain = NULL;
+	const ResolverOverrideTestRef me = (ResolverOverrideTestRef) inCtx;
+	SockAddrSetPort( &me->serverAddr, inDNSServerPort );
+	nw_resolver_config_t resolverConfig = nw_resolver_config_create();
+	require_action( resolverConfig, exit, err = kNoResourcesErr );
+	
+	nw_resolver_config_set_protocol( resolverConfig, nw_resolver_protocol_dns53 );
+	nw_resolver_config_set_identifier( resolverConfig, me->resolverUUID );
+	char serverAddrStr[ kSockAddrStringMaxSize ];
+	err = SockAddrToString( &me->serverAddr.sa, kSockAddrStringFlagsNone, serverAddrStr );
+	require_noerr( err, exit );
+	
+	nw_resolver_config_add_name_server( resolverConfig, serverAddrStr );
+	char tag[ 6 + 1 ];
+	_RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag );
+	ASPrintF( &bogusDomain, "resolver-override-test-%s.invalid", tag );
+	require_action( bogusDomain, exit, err = kNoMemoryErr );
+	
+	nw_resolver_config_add_match_domain( resolverConfig, bogusDomain );
+	uuid_t uuid;
+	uuid_string_t uuidStr;
+	nw_resolver_config_get_identifier( resolverConfig, uuid );
+	uuid_unparse_upper( uuid, uuidStr );
+	FPrintF( stdout, "%{du:time} âš’ Publishing resolver configuration (%s): %@\n", NULL, uuidStr, resolverConfig );
+	const bool ok = nw_resolver_config_publish( resolverConfig );
+	require_action( ok, exit, err = kUnknownErr );
+	
+	check( !me->resolverConfig );
+	me->resolverConfig = resolverConfig;
+	resolverConfig = NULL;
+	err = _ResolverOverrideTestStartSubtest( me );
+	require_noerr( err, exit );
+	
+exit:
+	ForgetMem( &bogusDomain );
+	nw_forget( &resolverConfig );
+	if( err ) _ResolverOverrideTestStop( me, err );
+}
+
+//===========================================================================================================================
+
+static void	_ResolverOverrideTestDNSServerStopHandler( const OSStatus inError, void * const inCtx )
+{
+	if( inError )
+	{
+		FPrintF( stdout, "%{du:time} ! DNS server stopped with unexpected error: %#m\n", NULL, inError );
+	}
+	const ResolverOverrideTestRef me = (ResolverOverrideTestRef) inCtx;
+	if( inError ) _ResolverOverrideTestStop( me, inError );
+	_ResolverOverrideTestRelease( me );
+}
+
+//===========================================================================================================================
+
+static void	_ResolverOverrideTestStart( const ResolverOverrideTestRef me )
+{
+	// Generate a resolver UUID.
+	
+	uuid_generate_random( me->resolverUUID );
+	
+	// Create a domain for DNS server.
+	
+	OSStatus err;
+	char tag[ 6 + 1 ];
+	_RandomStringExact( kLowerAlphaNumericCharSet, kLowerAlphaNumericCharSetSize, sizeof( tag ) - 1, tag );
+	check( !me->domain );
+	ASPrintF( &me->domain, "resolver-override-test-%s.test", tag );
+	require_action( me->domain, exit, err = kNoMemoryErr );
+	
+	// Set up a loopback server address for DNS server.
+	
+	_SockAddrInitIPv4( &me->serverAddr.v4, INADDR_LOOPBACK, 0 );
+	
+	// Start up a DNS server.
+	
+	err = _DNSServerCreate( me->queue, _ResolverOverrideTestDNSServerStartHandler, _ResolverOverrideTestDNSServerStopHandler,
+		me, 0, 60, &me->serverAddr, 1, me->domain, false, &me->server );
+	require_noerr( err, exit );
+	
+	_ResolverOverrideTestRetain( me );
+	_DNSServerStart( me->server );
+	
+exit:
+	if( err ) _ResolverOverrideTestStop( me, err );
+}
+
+//===========================================================================================================================
+
+static OSStatus	_ResolverOverrideTestRun( const ResolverOverrideTestRef me )
+{
+	dispatch_async( me->queue,
+	^{
+		_ResolverOverrideTestStart( me );
+	} );
+	dispatch_semaphore_wait( me->doneSem, DISPATCH_TIME_FOREVER );
+	return( me->error );
+}
+
+//===========================================================================================================================
+
+static void	ResolverOverrideTestCommand( void )
+{
+	OSStatus err;
+	ResolverOverrideTestRef test = _ResolverOverrideTestCreate( &err );
+	require_noerr( err, exit );
+	
+	err = _ResolverOverrideTestRun( test );
+	require_noerr( err, exit );
+	
+exit:
+	if( test ) _ResolverOverrideTestRelease( test );
+    gExitCode = err ? 1 : 0;
+}
+
+//===========================================================================================================================
 //	SSDPDiscoverCmd
 //===========================================================================================================================
 
@@ -29344,16 +33616,7 @@
 	dnssd_getaddrinfo_set_interface_index( me->gai, me->ifIndex );
 	dnssd_getaddrinfo_set_protocols( me->gai, me->protocols );
 	dnssd_getaddrinfo_set_use_failover( me->gai, me->useFailover );
-	if( __builtin_available( macOS 13.0, iOS 16.1, watchOS 9.1, tvOS 16.1, * ) )
-	{
-		dnssd_getaddrinfo_prohibit_encrypted_dns( me->gai, me->prohibitEncryptedDNS );
-	}
-	else
-	{
-		FPrintF( stderr, "dnssd_getaddrinfo_prohibit_encrypted_dns is not available on this OS." );
-		err = kUnsupportedErr;
-		goto exit;
-	}
+	dnssd_getaddrinfo_prohibit_encrypted_dns( me->gai, me->prohibitEncryptedDNS );
 	if( me->privateLogging )
 	{
 		dnssd_getaddrinfo_set_log_privacy_level( me->gai, dnssd_log_privacy_level_private );
@@ -29465,14 +33728,11 @@
 					FPrintF( stdout, "    (Not a known tracker)\n" );
 				}
 			}
-			if( __builtin_available( macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, * ) )
+			if( dnssd_getaddrinfo_result_has_extended_dns_error( result ) )
 			{
-				if( dnssd_getaddrinfo_result_has_extended_dns_error( result ) )
-				{
-					FPrintF( stdout, "    Extended DNS Error: {code: %u, extra-text: '%s'}\n",
-						dnssd_getaddrinfo_result_get_extended_dns_error_code( result ),
-						dnssd_getaddrinfo_result_get_extended_dns_error_text( result ) );
-				}
+				FPrintF( stdout, "    Extended DNS Error: {code: %u, extra-text: '%s'}\n",
+					dnssd_getaddrinfo_result_get_extended_dns_error_code( result ),
+					dnssd_getaddrinfo_result_get_extended_dns_error_text( result ) );
 			}
 		}
 		if( me->oneshot ) _GetAddrInfoNewCmdStopF( me, "one-shot done" );
@@ -29928,6 +34188,243 @@
 }
 
 //===========================================================================================================================
+//	WiFiOnCommand
+//===========================================================================================================================
+
+static void	WiFiOnCommand( void )
+{
+	OSStatus		err;
+	
+#if( TARGET_OS_OSX )
+	err = systemf( NULL, "/usr/sbin/networksetup -setairportpower Wi-Fi on" );
+#else
+	err = systemf( NULL, "/usr/local/bin/mobilewifitool -- manager power 1" );
+#endif
+	cli_require_noerr_return( err, "error: %#m\n", err );
+}
+
+//===========================================================================================================================
+//	WiFiOffCommand
+//===========================================================================================================================
+
+static void	WiFiOffCommand( void )
+{
+	OSStatus		err;
+	
+#if( TARGET_OS_OSX )
+	err = systemf( NULL, "/usr/sbin/networksetup -setairportpower Wi-Fi off" );
+#else
+	err = systemf( NULL, "/usr/local/bin/mobilewifitool -- manager power 0" );
+#endif
+	cli_require_noerr_return( err, "error: %#m\n", err );
+}
+
+//===========================================================================================================================
+//	DiscoveryProxyCommand
+//===========================================================================================================================
+
+static void	_DiscoveryProxyCmdSignalHandler( void *inContext );
+
+static void	DiscoveryProxyCommand( void )
+{
+	OSStatus							err;
+	mrc_discovery_proxy_parameters_t	params			= NULL;
+	mrc_discovery_proxy_t				proxy			= NULL;
+	dispatch_source_t					sigIntSource	= NULL;
+	dispatch_source_t					sigTermSource	= NULL;
+
+	params = mrc_discovery_proxy_parameters_create();
+	require_action( params, exit, err = kNoResourcesErr );
+
+	uint32_t ifIndex;
+	err = InterfaceIndexFromArgString( gInterface, &ifIndex );
+	require_noerr_quiet( err, exit );
+
+	mrc_discovery_proxy_parameters_set_interface( params, ifIndex );
+	for( size_t i = 0; i < gDiscoveryProxy_ServerAddrCount; ++i )
+	{
+		sockaddr_ip sip;
+		err = SockAddrFromArgString( gDiscoveryProxy_ServerAddrs[ i ], "server address", &sip );
+		require_noerr_quiet( err, exit );
+		
+		switch( sip.sa.sa_family )
+		{
+			case AF_INET:
+			{
+				const struct sockaddr_in * const sin = &sip.v4;
+				err = mrc_discovery_proxy_parameters_add_server_ipv4_address( params, ntohl( sin->sin_addr.s_addr ),
+					ntohs( sin->sin_port ) );
+				require_noerr_quiet( err, exit );
+				break;
+			}
+			case AF_INET6:
+			{
+				const struct sockaddr_in6 * const sin6 = &sip.v6;
+				err = mrc_discovery_proxy_parameters_add_server_ipv6_address( params, sin6->sin6_addr.s6_addr,
+					ntohs( sin6->sin6_port ), sin6->sin6_scope_id );
+				require_noerr_quiet( err, exit );
+				break;
+			}
+		}
+	}
+	for( size_t i = 0; i < gDiscoveryProxy_MatchDomainCount; ++i )
+	{
+		err = mrc_discovery_proxy_parameters_add_match_domain( params, gDiscoveryProxy_MatchDomains[ i ] );
+		require_noerr_quiet( err, exit );
+	}
+	for( size_t i = 0; i < gDiscoveryProxy_CertificateCount; ++i )
+	{
+		uint8_t *		certPtr;
+		size_t			certLen;
+		
+		err = HexToDataCopy( gDiscoveryProxy_Certificates[ i ], kSizeCString, kHexToData_DefaultFlags, &certPtr,
+			&certLen, NULL );
+		require_noerr_action( err, exit, FPrintF( stderr,
+			"error: Failed to parse identity reference hex string: '%s'\n", gDiscoveryProxy_Certificates[ i ] ) );
+		
+		mrc_discovery_proxy_parameters_add_server_certificate( params, certPtr, certLen );
+		ForgetMem( &certPtr );
+	}
+	proxy = mrc_discovery_proxy_create( params );
+	mrc_forget( &params );
+	require_action_quiet( proxy, exit, err = kNoResourcesErr );
+	
+	mrc_discovery_proxy_set_queue( proxy, dispatch_get_main_queue() );
+	mrc_discovery_proxy_set_event_handler( proxy,
+	^( const mrc_discovery_proxy_event_t inEvent, const OSStatus inError )
+	{
+		switch( inEvent )
+		{
+			case mrc_discovery_proxy_event_started:
+			{
+				FPrintF( stdout, "%{du:time}  Discovery Proxy was started\n", NULL );
+				break;
+			}
+			case mrc_discovery_proxy_event_interruption:
+			{
+				FPrintF( stdout, "%{du:time}  Discovery Proxy was interrupted\n", NULL );
+				break;
+			}
+			case mrc_discovery_proxy_event_invalidation:
+			{
+				mrc_release( proxy );
+				FPrintF( stdout, "---\n" );
+				FPrintF( stdout, "Error:    %#m\n", inError );
+				FPrintF( stdout, "End time: %{du:time}\n", NULL );
+				exit( 0 );
+			}
+			case mrc_discovery_proxy_event_none:
+				break;
+		}
+	} );
+	signal( SIGINT, SIG_IGN );
+	err = DispatchSignalSourceCreate( SIGINT, dispatch_get_main_queue(), _DiscoveryProxyCmdSignalHandler, proxy,
+		&sigIntSource );
+	require_noerr( err, exit );
+	dispatch_activate( sigIntSource );
+	
+	signal( SIGTERM, SIG_IGN );
+	err = DispatchSignalSourceCreate( SIGTERM, dispatch_get_main_queue(), _DiscoveryProxyCmdSignalHandler, proxy,
+		&sigTermSource );
+	require_noerr( err, exit );
+	dispatch_activate( sigTermSource );
+	
+	FPrintF( stdout, "Discovery Proxy:    %@\n", proxy );
+	FPrintF( stdout, "Start time: %{du:time}\n",	NULL );
+	FPrintF( stdout, "---\n" );
+	mrc_discovery_proxy_activate( proxy );
+	dispatch_main();
+	
+exit:
+	if( err ) ErrQuit( 1, "error: %#m\n", err );
+}
+
+static void	_DiscoveryProxyCmdSignalHandler( void *inContext )
+{
+	mrc_discovery_proxy_invalidate( (mrc_discovery_proxy_t) inContext );
+}
+
+//===========================================================================================================================
+//	CachedLocalRecordsCommand
+//===========================================================================================================================
+
+static void	CachedLocalRecordsCommand( void )
+{
+	OSStatus err;
+	dispatch_semaphore_t doneSem = NULL;
+	mrc_cached_local_records_inquiry_t inquiry = NULL;
+	dispatch_queue_t queue = dispatch_queue_create( "com.apple.dnssdutil.cached-local-records-command",
+		DISPATCH_QUEUE_SERIAL );
+	require_action( queue, exit, err = kNoResourcesErr );
+	
+	doneSem = dispatch_semaphore_create( 0 );
+	require_action( doneSem, exit, err = kNoResourcesErr );
+	
+	inquiry = mrc_cached_local_records_inquiry_create();
+	require_action( inquiry, exit, err = kNoResourcesErr );
+	
+	mrc_cached_local_records_inquiry_set_queue( inquiry, queue );
+	mrc_cached_local_records_inquiry_set_result_handler( inquiry,
+	^( const xpc_object_t inRecordInfo, const OSStatus inError )
+	{
+		if( inError )
+		{
+			FPrintF( stderr, "error: %#m\n", inError );
+		}
+		const size_t recordInfoCount = inRecordInfo ? xpc_array_get_count( inRecordInfo ) : 0;
+		FPrintF( stdout, "Record count: %zu\n", recordInfoCount );
+		for( size_t i = 0; i < recordInfoCount; ++i )
+		{
+			const xpc_object_t dict = xpc_array_get_dictionary( inRecordInfo, i );
+			if( !dict )
+			{
+				continue;
+			}
+			FPrintF( stdout, "Record %zu of %zu:\n", i + 1, recordInfoCount );
+			const char * const name = xpc_dictionary_get_string( dict, mrc_cached_local_record_key_name );
+			if( name )
+			{
+				FPrintF( stdout, "\tname:           %s\n", name );
+			}
+			const char * const first_label = xpc_dictionary_get_string( dict, mrc_cached_local_record_key_first_label );
+			if( first_label )
+			{
+				FPrintF( stdout, "\tfirst label:    %s\n", first_label );
+			}
+			const char * const sourceAddr = xpc_dictionary_get_string( dict, mrc_cached_local_record_key_source_address );
+			if( sourceAddr )
+			{
+				FPrintF( stdout, "\tsource address: %s\n", sourceAddr );
+			}
+			const char * const rdata = xpc_dictionary_get_string( dict, mrc_cached_local_record_key_rdata );
+			if( rdata )
+			{
+				FPrintF( stdout, "\trdata:          %s\n", rdata );
+			}
+			FPrintF( stdout, "\n" );
+		}
+		dispatch_semaphore_signal( doneSem );
+	} );
+	mrc_cached_local_records_inquiry_activate( inquiry );
+	
+	// Wait for the inquiry to complete.
+	// Note: We intentionally use a semaphore that will be signaled by the asynchronous handler. The analyzer considers
+	// this an antipattern, which is OK for the purposes of this internal test tool. Actual well-written production code
+	// would usually not have to emulate this type of synchronous behavior.
+	
+#if( !COMPILER_CLANG_ANALYZER )
+	dispatch_semaphore_wait( doneSem, DISPATCH_TIME_FOREVER );
+#endif
+	err = kNoErr;
+	
+exit:
+	dispatch_forget( &queue );
+	dispatch_forget( &doneSem );
+	mrc_forget( &inquiry );
+	gExitCode = err ? 1 : 0;
+}
+
+//===========================================================================================================================
 //	DaemonVersionCmd
 //===========================================================================================================================
 
@@ -34266,6 +38763,7 @@
 			mdns_signed_resolve_result_t		signedResult;
 			
 			signedResult = mdns_signed_resolve_result_create_from_data( dataPtr, dataLen, &err );
+			bc_ulog( kLogLevelTrace, "Signed resolve result -- %@", signedResult );
 			if( signedResult )
 			{
 				if( mdns_signed_resolve_result_covers_txt_rdata( signedResult, instance->txtPtr, instance->txtLen ) )
@@ -34431,7 +38929,7 @@
 					mdns_signed_hostname_result_t		signedResult;
 					
 					signedResult = mdns_signed_hostname_result_create_from_data( dataPtr, dataLen, &createErr );
-					sb_ulog( kLogLevelTrace, "Signed hostname result: %@", signedResult );
+					sb_ulog( kLogLevelTrace, "Signed hostname result -- %@", signedResult );
 					if( signedResult )
 					{
 						validated = true;
@@ -34509,7 +39007,7 @@
 				mdns_signed_hostname_result_t		signedResult;
 				
 				signedResult = mdns_signed_hostname_result_create_from_data( dataPtr, dataLen, &createErr );
-				sb_ulog( kLogLevelTrace, "Signed hostname result: %@", signedResult );
+				sb_ulog( kLogLevelTrace, "Signed hostname result -- %@", signedResult );
 				if( signedResult )
 				{
 					validated = true;
diff --git a/Clients/srputil/srputil.c b/Clients/srputil/srputil.c
index 60a7b0d..29950f1 100644
--- a/Clients/srputil/srputil.c
+++ b/Clients/srputil/srputil.c
@@ -1,6 +1,6 @@
 /* srputil.c
  *
- * Copyright (c) 2020-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2020-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -43,6 +43,12 @@
 #include "ioloop.h"
 #include "advertising_proxy_services.h"
 #include "route-tracker.h"
+#include "state-machine.h"
+#include "thread-service.h"
+#include "service-tracker.h"
+#include "probe-srp.h"
+#include "cti-services.h"
+#include "adv-ctl-server.h"
 
 
 static void
@@ -361,6 +367,16 @@
     exit(0);
 }
 
+static void
+start_thread_shutdown_callback(advertising_proxy_conn_ref cref, void *result, advertising_proxy_error_type err)
+{
+    INFO("cref %p  response %p   err %d.", cref, result, err);
+    if (err != kDNSSDAdvertisingProxyStatus_NoError) {
+        exit(1);
+    }
+    exit(0);
+}
+
 typedef struct variable variable_t;
 struct variable {
     variable_t *next;
@@ -388,6 +404,89 @@
 bool do_tcp_fin_length = false;
 bool do_tcp_fin_payload = false;
 
+service_tracker_t *tracker;
+
+// Dummy functions required to use service tracker here
+void
+adv_ctl_thread_shutdown_status_check(srp_server_t *UNUSED server_state) {
+}
+
+static void
+service_done_callback(void *context, cti_status_t status)
+{
+    const char *action = context;
+    if (status != kCTIStatus_NoError) {
+        fprintf(stderr, PUB_S_SRP " failed, status %d", action, status);
+        exit(1);
+    } else {
+        fprintf(stderr, PUB_S_SRP " done", action);
+        exit(0);
+    }
+}
+
+static void
+service_set_changed(bool unicast)
+{
+    thread_service_t *winner = NULL;
+    for (thread_service_t *service = service_tracker_services_get(tracker); service != NULL; service = service->next) {
+        if (service->ignore) {
+            continue;
+        }
+        if (unicast && service->service_type == unicast_service) {
+            if (winner == NULL || in6addr_compare(&service->u.unicast.address, &winner->u.unicast.address) < 0) {
+                winner = service;
+            }
+        }
+    }
+    if (winner == NULL) {
+        fprintf(stderr, "no services present!");
+        exit(1);
+    }
+    winner->u.unicast.address.s6_addr[15] = 0;
+    uint8_t service_data[1] = { THREAD_SRP_SERVER_OPTION };
+    uint8_t server_data[18];
+    memcpy(server_data, &winner->u.unicast.address, 15);
+    server_data[15] = 0;
+    server_data[16] = winner->u.unicast.port[0];
+    server_data[17] = winner->u.unicast.port[1];
+    int ret = cti_add_service(NULL, "add", service_done_callback, NULL,
+                              THREAD_ENTERPRISE_NUMBER, service_data, 1, server_data, 18);
+    if (ret != kCTIStatus_NoError) {
+        fprintf(stderr, "add_service failed: %d", ret);
+        exit(1);
+    }
+}
+
+static void
+unicast_service_set_changed(void *UNUSED context)
+{
+    service_set_changed(true);
+}
+
+static void
+start_advertising_winning_unicast_service(void)
+{
+    tracker = service_tracker_create(NULL);
+    if (tracker != NULL) {
+        service_tracker_callback_add(tracker, unicast_service_set_changed, NULL, NULL);
+        service_tracker_start(tracker);
+    } else {
+        fprintf(stderr, "unable to allocate tracker");
+        exit(1);
+    }
+}
+
+static void
+start_removing_unicast_service(void)
+{
+    uint8_t service_data[1] = { THREAD_SRP_SERVER_OPTION };
+    int ret = cti_remove_service(NULL, "remove", service_done_callback, NULL, THREAD_ENTERPRISE_NUMBER, service_data, 1);
+    if (ret != kCTIStatus_NoError) {
+        fprintf(stderr, "remove_service failed: %d", ret);
+        exit(1);
+    }
+}
+
 static void
 tcp_datagram_callback(comm_t *NONNULL comm, message_t *NONNULL message, void *NULLABLE context)
 {
@@ -458,40 +557,170 @@
     return kDNSSDAdvertisingProxyStatus_NoError;
 }
 
+const char *need_name, *need_service;
+bool needed_flag;
+
+advertising_proxy_conn_ref service_sub;
+
+static void
+service_callback(advertising_proxy_conn_ref UNUSED sub, void *UNUSED context, advertising_proxy_error_type error)
+{
+    fprintf(stderr, "service callback: %d\n", error);
+    exit(0);
+}
+
+static void
+start_needing_service(void)
+{
+    advertising_proxy_error_type ret = advertising_proxy_set_service_needed(&service_sub, dispatch_get_main_queue(),
+                                                                            service_callback, NULL, NULL, need_service,
+                                                                            needed_flag);
+    if (ret != kDNSSDAdvertisingProxyStatus_NoError) {
+        fprintf(stderr, "advertising_proxy_service_create failed: %d\n", ret);
+        exit(1);
+    }
+}
+
+advertising_proxy_conn_ref instance_sub;
+
+static void
+instance_callback(advertising_proxy_conn_ref UNUSED sub, void *UNUSED context, advertising_proxy_error_type error)
+{
+    fprintf(stderr, "instance callback: %d\n", error);
+    exit(0);
+}
+
+static void
+start_needing_instance(void)
+{
+    advertising_proxy_error_type ret = advertising_proxy_set_service_needed(&instance_sub, dispatch_get_main_queue(),
+                                                                            instance_callback, NULL, need_name,
+                                                                            need_service, needed_flag);
+    if (ret != kDNSSDAdvertisingProxyStatus_NoError) {
+        fprintf(stderr, "advertising_proxy_set_service_needed failed: %d\n", ret);
+        exit(1);
+    }
+}
+
+const char *browse_service, *resolve_name, *resolve_service;
+
+advertising_proxy_subscription_t *browse_sub;
+
+static void
+browse_callback(advertising_proxy_subscription_t *UNUSED sub, advertising_proxy_error_type error, uint32_t interface_index,
+                bool add, const char *instance_name, const char *service_type, void *UNUSED context)
+{
+    if (error != kDNSSDAdvertisingProxyStatus_NoError) {
+        fprintf(stderr, "browse_callback: %d\n", error);
+        exit(1);
+    }
+
+    fprintf(stderr, "browse: %d %s %s %s\n", interface_index, add ? "add" : "rmv", instance_name, service_type);
+}
+
+static void
+start_browsing_service(void)
+{
+    advertising_proxy_error_type ret = advertising_proxy_browse_create(&browse_sub, dispatch_get_main_queue(),
+                                                                        browse_service, browse_callback, NULL);
+    if (ret != kDNSSDAdvertisingProxyStatus_NoError) {
+        fprintf(stderr, "advertising_proxy_browse_create failed: %d\n", ret);
+        exit(1);
+    }
+}
+
+advertising_proxy_subscription_t *resolve_sub;
+
+static void
+resolve_callback(advertising_proxy_subscription_t *UNUSED sub, advertising_proxy_error_type error,
+                 uint32_t interface_index, bool add, const char *fullname, const char *hostname, uint16_t port,
+                 uint16_t txt_length, const uint8_t *UNUSED txt_record, void *UNUSED context)
+{
+    if (error != kDNSSDAdvertisingProxyStatus_NoError) {
+        fprintf(stderr, "resolve_create callback: %d\n", error);
+        exit(1);
+    }
+
+    fprintf(stderr, "resolved: %d %s %s %s %d %d\n", interface_index, add ? "add" : "rmv",
+            fullname, hostname, port, txt_length);
+}
+
+static void
+start_resolving_service(void)
+{
+    advertising_proxy_error_type ret = advertising_proxy_resolve_create(&resolve_sub, dispatch_get_main_queue(),
+                                                                        resolve_name, resolve_service, NULL,
+                                                                        resolve_callback, NULL);
+    if (ret != kDNSSDAdvertisingProxyStatus_NoError) {
+        fprintf(stderr, "advertising_proxy_resolve_create failed: %d\n", ret);
+        exit(1);
+    }
+}
+
+advertising_proxy_subscription_t *registrar_sub;
+
+static void
+registrar_callback(advertising_proxy_subscription_t *UNUSED sub,
+                   advertising_proxy_error_type error, void *UNUSED context)
+{
+    if (error != kDNSSDAdvertisingProxyStatus_NoError) {
+        fprintf(stderr, "registrar_callback: %d\n", error);
+        exit(1);
+    }
+
+    INFO("SRP registrar is enabled.");
+}
+
+static void
+start_registrar(void)
+{
+    advertising_proxy_error_type ret = advertising_proxy_registrar_create(&registrar_sub, dispatch_get_main_queue(),
+                                                                          registrar_callback, NULL);
+    if (ret != kDNSSDAdvertisingProxyStatus_NoError) {
+        fprintf(stderr, "advertising_proxy_registrar_create failed: %d", ret);
+        exit(1);
+    }
+}
+
+
 static void
 usage(void)
 {
-    fprintf(stderr, "srputil start                     -- start the SRP MDNS Proxy through launchd\n");
-    fprintf(stderr,
-            "        tcp-zero                  -- connect to port 53, send a DNS message that's got a zero-length payload\n");
-    fprintf(stderr,
-            "        tcp-fin-length            -- connect to port 53, send a DNS message that ends before length is complete\n");
-    fprintf(stderr,
-            "        tcp-fin-payload           -- connect to port 53, send a DNS message that ends before payload is complete\n");
-    fprintf(stderr, "        services                  -- get the list of services currently being advertised\n");
-    fprintf(stderr, "        block                     -- block the SRP listener\n");
-    fprintf(stderr, "        unblock                   -- unblock the SRP listener\n");
-    fprintf(stderr, "        regenerate-ula            -- generate a new ULA and restart the network\n");
-    fprintf(stderr, "        adv-prefix-high           -- advertise high-priority prefix to thread network\n");
-    fprintf(stderr, "        adv-prefix                -- advertise prefix to thread network\n");
-    fprintf(stderr, "        stop                      -- stop advertising as SRP server\n");
-    fprintf(stderr, "        get-ula                   -- fetch the current ULA prefix configured on the SRP server\n");
-    fprintf(stderr, "        disable-srpl              -- disable SRP replication\n");
-    fprintf(stderr, "        add-prefix <ipv6 prefix>     -- add an OMR prefix\n");
-    fprintf(stderr, "        remove-prefix <ipv6 prefix>  -- remove an OMR prefix\n");
-    fprintf(stderr, "        add-nat64-prefix <nat64 ipv6 prefix>     -- add an nat64 prefix\n");
-    fprintf(stderr, "        remove-nat64-prefix <nat64 ipv6 prefix>  -- remove an nat64 prefix\n");
-    fprintf(stderr, "        drop-srpl-connection      -- drop existing srp replication connections\n");
-    fprintf(stderr, "        undrop-srpl-connection    -- restart srp replication connections that were dropped \n");
-    fprintf(stderr, "        drop-srpl-advertisement   -- stop advertising srpl service (but keep it around)\n");
-    fprintf(stderr, "        undrop-srpl-advertisement -- resume advertising srpl service\n");
-    fprintf(stderr, "        start-dropping-push       -- start repeatedly dropping any active push connections after 90 seconds\n");
-    fprintf(stderr, "        start-breaking-time       -- start breaking time validation on replicated SRP registrations\n");
-    fprintf(stderr, "        set [variable] [value]    -- set the value of variable to value (e.g. set min-lease-time 100)\n");
-    fprintf(stderr, "        block-anycast-service      -- block advertising anycast service\n");
-    fprintf(stderr, "        unblock-anycast-service    -- unblock advertising anycast service\n");
+    fprintf(stderr, "srputil start [if1 .. ifN -]            -- start the SRP MDNS Proxy through launchd\n");
+    fprintf(stderr, "  tcp-zero                              -- connect to port 53, send a DNS message that's got a zero-length payload\n");
+    fprintf(stderr, "  tcp-fin-length                        -- connect to port 53, send a DNS message that ends before length is complete\n");
+    fprintf(stderr, "  tcp-fin-payload                       -- connect to port 53, send a DNS message that ends before payload is complete\n");
+    fprintf(stderr, "  services                              -- get the list of services currently being advertised\n");
+    fprintf(stderr, "  block                                 -- block the SRP listener\n");
+    fprintf(stderr, "  unblock                               -- unblock the SRP listener\n");
+    fprintf(stderr, "  regenerate-ula                        -- generate a new ULA and restart the network\n");
+    fprintf(stderr, "  adv-prefix-high                       -- advertise high-priority prefix to thread network\n");
+    fprintf(stderr, "  adv-prefix                            -- advertise prefix to thread network\n");
+    fprintf(stderr, "  stop                                  -- stop advertising as SRP server\n");
+    fprintf(stderr, "  get-ula                               -- fetch the current ULA prefix configured on the SRP server\n");
+    fprintf(stderr, "  disable-srpl                          -- disable SRP replication\n");
+    fprintf(stderr, "  add-prefix <ipv6 prefix>              -- add an OMR prefix\n");
+    fprintf(stderr, "  remove-prefix <ipv6 prefix            -- remove an OMR prefix\n");
+    fprintf(stderr, "  add-nat64-prefix <nat64 prefix>       -- add an nat64 prefix\n");
+    fprintf(stderr, "  remove-nat64-prefix <nat64 prefix>    -- remove an nat64 prefix\n");
+    fprintf(stderr, "  drop-srpl-connection                  -- drop existing srp replication connections\n");
+    fprintf(stderr, "  undrop-srpl-connection                -- restart srp replication connections that were dropped \n");
+    fprintf(stderr, "  drop-srpl-advertisement               -- stop advertising srpl service (but keep it around)\n");
+    fprintf(stderr, "  undrop-srpl-advertisement             -- resume advertising srpl service\n");
+    fprintf(stderr, "  start-dropping-push                   -- start repeatedly dropping any active push connections after 90 seconds\n");
+    fprintf(stderr, "  start-breaking-time                   -- start breaking time validation on replicated SRP registrations\n");
+    fprintf(stderr, "  set [variable] [value]                -- set the value of variable to value (e.g. set min-lease-time 100)\n");
+    fprintf(stderr, "  block-anycast-service                 -- block advertising anycast service\n");
+    fprintf(stderr, "  unblock-anycast-service               -- unblock advertising anycast service\n");
+    fprintf(stderr, "  start-thread-shutdown                 -- start thread network shutdown\n");
+    fprintf(stderr, "  advertise-winning-unicast-service     -- advertise a unicast service that wins over the current service\n");
+    fprintf(stderr, "  browse <service>                      -- start an advertising_proxy_browse on the specified service\n");
+    fprintf(stderr, "  resolve <name> <service>              -- start an advertising_proxy_resolve on the specified service instance\n");
+    fprintf(stderr, "  need-service <service> <flag>         -- signal to srp-mdns-proxy that we need to discover a service\n");
+    fprintf(stderr, "  need-instance <name> <service> <flag> -- signal to srp-mdns-proxy that we need to discover a service\n");
+    fprintf(stderr, "  start-srp                             -- on thread device, enable srp registration\n");
 #ifdef NOTYET
-    fprintf(stderr, "        flush                     -- flush all entries from the SRP proxy (for testing only)\n");
+    fprintf(stderr, "  flush                                 -- flush all entries from the SRP proxy (for testing only)\n");
 #endif
 }
 
@@ -521,12 +750,18 @@
 bool test_route_tracker = false;
 bool block_anycast_service = false;
 bool unblock_anycast_service = false;
+bool start_thread_shutdown = false;
+bool advertise_winning_unicast_service = false;
+bool remove_unicast_service = false;
+bool start_srp = false;
 uint8_t prefix_buf[16];
 #ifdef NOTYET
 bool watch = false;
 bool get = false;
 #endif
 variable_t *variables;
+int num_permitted_interfaces;
+char **permitted_interfaces;
 
 static void
 start_activities(void *context)
@@ -602,9 +837,36 @@
     if (err == kDNSSDAdvertisingProxyStatus_NoError && unblock_anycast_service) {
         err = advertising_proxy_unblock_anycast_service(&cref, main_queue, unblock_anycast_service_callback);
     }
+    if (err == kDNSSDAdvertisingProxyStatus_NoError && start_thread_shutdown) {
+        err = advertising_proxy_start_thread_shutdown(&cref, main_queue, start_thread_shutdown_callback);
+    }
     if (err == kDNSSDAdvertisingProxyStatus_NoError && test_route_tracker) {
         route_tracker_test_start(1000);
     }
+    if (err == kDNSSDAdvertisingProxyStatus_NoError && advertise_winning_unicast_service) {
+        start_advertising_winning_unicast_service();
+    }
+    if (err == kDNSSDAdvertisingProxyStatus_NoError && remove_unicast_service) {
+        start_removing_unicast_service();
+    }
+    if (err == kDNSSDAdvertisingProxyStatus_NoError && advertise_winning_unicast_service) {
+        start_advertising_winning_unicast_service();
+    }
+    if (err == kDNSSDAdvertisingProxyStatus_NoError && browse_service != NULL) {
+        start_browsing_service();
+    }
+    if (err == kDNSSDAdvertisingProxyStatus_NoError && resolve_service != NULL) {
+        start_resolving_service();
+    }
+    if (err == kDNSSDAdvertisingProxyStatus_NoError && need_service != NULL && need_name == NULL) {
+        start_needing_service();
+    }
+    if (err == kDNSSDAdvertisingProxyStatus_NoError && need_service != NULL && need_name != NULL) {
+        start_needing_instance();
+    }
+    if (err == kDNSSDAdvertisingProxyStatus_NoError && start_srp) {
+        start_registrar();
+    }
     if (err == kDNSSDAdvertisingProxyStatus_NoError && variables != NULL) {
         for (variable_t *variable = variables; variable != NULL; variable = variable->next) {
             err = advertising_proxy_set_variable(&cref, main_queue, set_variable_callback, variable, variable->name, variable->value);
@@ -651,6 +913,15 @@
         if (!strcmp(argv[i], "start")) {
 			start_proxy = true;
             something = true;
+            int j;
+            for (j = i + 1; j < argc; j++) {
+                if (!strcmp(argv[j], "-")) {
+                    break;
+                }
+            }
+            num_permitted_interfaces = j - i - 1;
+            permitted_interfaces = argv + i + 1;
+            i = j;
         } else if (!strcmp(argv[i], "tcp-zero")) {
             do_tcp_zero_test = true;
             something = true;
@@ -695,37 +966,96 @@
             disable_srp_replication = true;
             something = true;
         } else if (!strcmp(argv[i], "add-prefix")) {
+            if (i + 1 >= argc) {
+                usage();
+            }
             if (inet_pton(AF_INET6, argv[i + 1], prefix_buf) < 1) {
-                fprintf(stderr, "Wrong ipv6 prefix.\n");
+                fprintf(stderr, "Invalid ipv6 prefix %s.\n", argv[i + 1]);
+                usage();
             } else {
                 add_thread_prefix = true;
                 something = true;
                 i++;
             }
         } else if (!strcmp(argv[i], "remove-prefix")) {
+            if (i + 1 >= argc) {
+                usage();
+            }
             if (inet_pton(AF_INET6, argv[i + 1], prefix_buf) < 1) {
-                fprintf(stderr, "Wrong ipv6 prefix.\n");
+                fprintf(stderr, "Invalid ipv6 prefix %s.\n", argv[i + 1]);
+                usage();
             } else {
                 remove_thread_prefix = true;
                 something = true;
                 i++;
             }
         } else if (!strcmp(argv[i], "add-nat64-prefix")) {
+            if (i + 1 >= argc) {
+                usage();
+            }
             if (inet_pton(AF_INET6, argv[i + 1], prefix_buf) < 1) {
-                fprintf(stderr, "Wrong ipv6 prefix.\n");
+                fprintf(stderr, "Invalid ipv6 prefix %s.\n", argv[i + 1]);
+                usage();
             } else {
                 add_nat64_prefix = true;
                 something = true;
                 i++;
             }
         } else if (!strcmp(argv[i], "remove-nat64-prefix")) {
+            if (i + 1 >= argc) {
+                usage();
+            }
             if (inet_pton(AF_INET6, argv[i + 1], prefix_buf) < 1) {
-                fprintf(stderr, "Wrong ipv6 prefix.\n");
+                fprintf(stderr, "Invalid ipv6 prefix %s.\n", argv[i + 1]);
+                usage();
             } else {
                 remove_nat64_prefix = true;
                 something = true;
                 i++;
             }
+        } else if (!strcmp(argv[i], "browse")) {
+            if (i + 1 >= argc) {
+                usage();
+            }
+            browse_service = argv[i + 1];
+            i++;
+            something = true;
+        } else if (!strcmp(argv[i], "resolve")) {
+            if (i + 2 >= argc) {
+                usage();
+            }
+            resolve_name = argv[i + 1];
+            resolve_service = argv[i + 2];
+            i += 2;
+            something = true;
+        } else if (!strcmp(argv[i], "need-service")) {
+            if (i + 2 >= argc) {
+                usage();
+            }
+            need_service = argv[i + 1];
+            if (!strcmp(argv[i + 2], "true")) {
+                needed_flag = true;
+            } else {
+                needed_flag = false;
+            }
+            i += 2;
+            something = true;
+        } else if (!strcmp(argv[i], "need-instance")) {
+            if (i + 3 >= argc) {
+                usage();
+            }
+            need_name = argv[i + 1];
+            need_service = argv[i + 2];
+            if (!strcmp(argv[i + 3], "true")) {
+                needed_flag = true;
+            } else {
+                needed_flag = false;
+            }
+            i += 3;
+            something = true;
+        } else if (!strcmp(argv[i], "start-srp")) {
+            start_srp = true;
+            something = true;
         } else if (!strcmp(argv[i], "drop-srpl-connection")) {
             drop_srpl_connection = true;
             something = true;
@@ -753,6 +1083,15 @@
         } else if (!strcmp(argv[i], "test-route-tracker")) {
             test_route_tracker = true;
             something = true;
+        } else if (!strcmp(argv[i], "start-thread-shutdown")) {
+            start_thread_shutdown = true;
+            something = true;
+        } else if (!strcmp(argv[i], "advertise-winning-unicast-service")) {
+            advertise_winning_unicast_service = true;
+            something = true;
+        } else if (!strcmp(argv[i], "remove-unicast-service")) {
+            remove_unicast_service = true;
+            something = true;
         } else if (!strcmp(argv[i], "set")) {
             if (i + 2 >= argc) {
                 usage();
diff --git a/DSO/dso-transport.c b/DSO/dso-transport.c
index 39c8fdc..5d59903 100644
--- a/DSO/dso-transport.c
+++ b/DSO/dso-transport.c
@@ -55,6 +55,7 @@
 #include "mDNSMacOSX.h"
 #endif
 
+#include "mDNSFeatures.h"
 #include "mdns_strict.h"
 
 extern mDNS mDNSStorage;
@@ -1104,13 +1105,13 @@
 }
 
 static bool tls_cert_verify(const sec_protocol_metadata_t metadata, const sec_trust_t trust_ref,
-                            const uint32_t cs_serial)
+    const bool trusts_alternative_trusted_server_certificates, const uint32_t cs_serial)
 {
     bool valid;
 
     // When iCloud keychain is enabled, it is possible that the TLS certificate that DNS push server
     // uses has been synced to the client device, so we do the evaluation here.
-    const tls_keychain_context_t context = {metadata, trust_ref};
+    const tls_keychain_context_t context = {metadata, trust_ref, trusts_alternative_trusted_server_certificates};
     valid = tls_cert_evaluate(&context);
     if (valid) {
         // If the evaluation succeeds, it means that the DNS push server that mDNSResponder is
@@ -1132,16 +1133,6 @@
                   cs_serial);
     }
 
-    // Ideally, We should support case 1 and case 2, case 4, but avoid case 3.
-    // However, given that:
-    // 1. mDNSResponder only connects to the DNS push server on the same local subnet, which
-    //    means the malicious DNS push server has to be present in the local network (at home,
-    //    , at office), the probability of this scenario is relatively small.
-    // 2. Service discovery via multicast DNS or unicast DNS has no security protection.
-    // It is OK for now to blindly trust the TLS certificate from the DNS push server, which means we
-    // will not avoid case 3, just like service discovery via multicast DNS or unicast DNS.
-    valid = true;
-
     return valid;
 }
 
@@ -1202,12 +1193,17 @@
     nw_parameters_configure_protocol_block_t configure_tls = NW_PARAMETERS_DISABLE_PROTOCOL;
     if (cs->tls_enabled) {
         const uint32_t cs_serial = cs->serial;
+        bool trusts_alternative_server_certificates = false;
+    #if MDNSRESPONDER_SUPPORTS(APPLE, TERMINUS_ASSISTED_UNICAST_DISCOVERY)
+        trusts_alternative_server_certificates = cs->trusts_alternative_server_certificates;
+    #endif
         configure_tls = ^(nw_protocol_options_t tls_options) {
             sec_protocol_options_t sec_options = nw_tls_copy_sec_protocol_options(tls_options);
             sec_protocol_options_set_verify_block(sec_options,
                 ^(sec_protocol_metadata_t metadata, sec_trust_t trust_ref, sec_protocol_verify_complete_t complete)
                 {
-                    const bool valid = tls_cert_verify(metadata, trust_ref, cs_serial);
+                    const bool valid = tls_cert_verify(metadata, trust_ref, trusts_alternative_server_certificates,
+                        cs_serial);
                     complete(valid);
                 },
                 dso_dispatch_queue);
@@ -1272,6 +1268,14 @@
                     LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
                         "[DSOC%u->C%" PRIu64 "] Connection to server: " PRI_IP_ADDR ":%u ready.",
                         serial, nw_connection_id, &addr, mDNSVal16(port));
+
+                    // Get the interface index from the path.
+                    nw_path_t curr_path = nw_connection_copy_current_path(connection);
+                    if (curr_path) {
+                        ncs->if_idx = nw_path_get_interface_index(curr_path);
+                    }
+                    MDNS_DISPOSE_NW(curr_path);
+
                     ncs->connecting = mDNSfalse;
                     mDNS_Lock(&mDNSStorage);
                     dso_connection_succeeded(ncs);
@@ -1493,7 +1497,7 @@
             // will notice that and terminate the connection by itself.
             LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
                 "[DSOC%u] The address being removed has been tried for the connection or is being used right now - "
-                "address: " PRI_IP_ADDR ":%u.", cs->serial, &addr_changed, mDNSVal16(cs->config_port));
+                "address: " PRI_IP_ADDR ":%u.", cs->serial, addr_changed, mDNSVal16(cs->config_port));
         }
     }
 
@@ -1576,13 +1580,6 @@
         fullname, flags, if_name, interfaceIndex, errorCode, fullname, &addr_changed, mDNSVal16(cs->config_port), ttl,
         BOOL_PARAM(on_the_local_subnet));
 
-    // Currently, mDNSResponder only trusts DNS push server on the local subnet.
-    if (!on_the_local_subnet) {
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
-            "[DSOC%u] Ignoring the DNS push server address that is not on the local subnet.", cs->serial);
-         goto exit;
-    }
-
     dso_connect_state_process_address_change(&addr_changed, addr_add, cs);
 
 exit:
diff --git a/DSO/dso-transport.h b/DSO/dso-transport.h
index ff4edb3..c64906e 100644
--- a/DSO/dso-transport.h
+++ b/DSO/dso-transport.h
@@ -130,6 +130,13 @@
     mDNSBool canceled; // Indicates if the dso_connect_state_t has been canceled and should be ignored for processing.
 
     mDNSBool connecting;
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, TERMINUS_ASSISTED_UNICAST_DISCOVERY)
+    // A boolean value to indicate whether the dso session should use the preconfigured TLS server certificates to
+    // perform the trust evaluation.
+    mDNSBool trusts_alternative_server_certificates;
+#endif
+
     mDNSIPPort config_port, connect_port;
     dso_transport_t *transport;
 #ifdef DSO_USES_NETWORK_FRAMEWORK
@@ -142,6 +149,7 @@
     int max_outstanding_queries;
     mDNSs32 last_event;
     mDNSs32 reconnect_time;
+    mDNSu32 if_idx; // The index of the interface where the DSO session is established through.
 };
 
 typedef struct {
diff --git a/DSO/dso.c b/DSO/dso.c
index 78580c8..c86cba6 100644
--- a/DSO/dso.c
+++ b/DSO/dso.c
@@ -1,6 +1,6 @@
 /* dso.c
  *
- * Copyright (c) 2018-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2018-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -40,6 +40,7 @@
 
 #include "DNSCommon.h"
 #include "mDNSEmbeddedAPI.h"
+#include "PlatformCommon.h"
 #include "dso.h"
 #include "DebugServices.h"   // For check_compile_time
 
@@ -283,6 +284,9 @@
     dso->additl = dso->additl_buf;
     dso->max_additls = MAX_ADDITLS;
 
+    dso->keepalive_interval = 3600 * MSEC_PER_SEC;
+    dso->inactivity_timeout = 15 * MSEC_PER_SEC;
+
     dso->next = dso_connections;
     dso_connections = dso;
 
@@ -567,6 +571,17 @@
     return disassociated_count;
 }
 
+void dso_update_outstanding_query_context(dso_state_t *const dso, const void *const old_context,
+    void *const new_context)
+{
+    dso_outstanding_query_state_t *const states = dso->outstanding_queries;
+    for (int i = 0; i < states->max_outstanding_queries; i++) {
+        if (states->queries[i].context == old_context) {
+            states->queries[i].context = new_context;
+        }
+    }
+}
+
 uint32_t dso_connections_reset_outstanding_query_context(const void *const context)
 {
     uint32_t reset_count = 0;
@@ -693,7 +708,7 @@
     }
 }
 
-void dso_keepalive(dso_state_t *dso, const DNSMessageHeader *header)
+void dso_keepalive(dso_state_t *dso, const DNSMessageHeader *header, bool response)
 {
     dso_keepalive_context_t context;
     memset(&context, 0, sizeof context);
@@ -704,6 +719,11 @@
         }
         return;
     }
+    if (dso->is_server && response) {
+        LogMsg("Dropping Keepalive Response received by DSO server");
+        return;
+    }
+
     memcpy(&context, dso->primary.payload, dso->primary.length);
     context.inactivity_timeout = ntohl(context.inactivity_timeout);
     context.keepalive_interval = ntohl(context.keepalive_interval);
@@ -912,7 +932,7 @@
     // Call the callback with the message or response
     if (dso->cb) {
         if (message_length != 12 && dso->primary.opcode == kDSOType_Keepalive) {
-            dso_keepalive(dso, header);
+            dso_keepalive(dso, header, response);
         } else if (message_length != 12 && dso->primary.opcode == kDSOType_RetryDelay) {
             dso_retry_delay(dso, header);
         } else {
@@ -979,6 +999,8 @@
         CASE_TO_STR(Keepalive);
         CASE_TO_STR(KeepaliveRcvd);
         CASE_TO_STR(RetryDelay);
+        MDNS_COVERED_SWITCH_DEFAULT:
+            break;
     }
 #undef CASE_TO_STR
     LogMsg("Invalid dso_event_type - dso_event_type: %d.", dso_event_type);
diff --git a/DSO/dso.h b/DSO/dso.h
index 88c9fd9..b6adac9 100644
--- a/DSO/dso.h
+++ b/DSO/dso.h
@@ -239,12 +239,31 @@
                                             void *context, void (*finalize)(dso_activity_t *));
 void dso_drop_activity(dso_state_t *dso, dso_activity_t *activity);
 uint32_t dso_ignore_further_responses(dso_state_t *dso, const void *context);
+
+/*!
+ *  @brief
+ *      Update the context of the outstanding queries in the DSO state.
+ *
+ *  @param dso
+ *      The reference to the DSO state.
+ *
+ *  @param old_context
+ *      The original context of the outstanding query to be updated.
+ *
+ *  @param new_context
+ *      The new context to set for the same outstanding query.
+ *
+ *  @discussion
+ *      This function must be called to update the the query context if the new one invalidates the old one.
+ */
+void dso_update_outstanding_query_context(dso_state_t *dso, const void *old_context, void *new_context);
+
 uint32_t dso_connections_reset_outstanding_query_context(const void *context);
 bool dso_make_message(dso_message_t *state, uint8_t *outbuf, size_t outbuf_size, dso_state_t *dso,
                       bool unidirectional, bool response, uint16_t xid, int rcode, void *callback_state);
 size_t dso_message_length(dso_message_t *state);
 void dso_retry_delay(dso_state_t *dso, const DNSMessageHeader *header);
-void dso_keepalive(dso_state_t *dso, const DNSMessageHeader *header);
+void dso_keepalive(dso_state_t *dso, const DNSMessageHeader *header, bool response);
 void dso_message_received(dso_state_t *dso, const uint8_t *message, size_t message_length, void *context);
 void dns_message_received(dso_state_t *dso, const uint8_t *message, size_t message_length, void *context);
 
diff --git a/Documents/Start mDNSResponder in Xcode.rtfd/TXT.rtf b/Documents/Start mDNSResponder in Xcode.rtfd/TXT.rtf
index 340c2dc..573ae05 100644
--- a/Documents/Start mDNSResponder in Xcode.rtfd/TXT.rtf
+++ b/Documents/Start mDNSResponder in Xcode.rtfd/TXT.rtf
@@ -15,7 +15,7 @@
 \
 You can also just double click on the project from Finder.\
 \
-2.) Configure Xcode Project Scheme by adding the following three arguments, -d -NoSandbox -UseDebugSocket.\
+2.) Configure Xcode Project Scheme by adding the following two arguments: -d -UseDebugSocket. (-NoSandbox is no longer necessary, nor supported.)\
 \
 \pard\tx720\tx1440\tx2160\tx2880\tx3600\tx4320\tx5040\tx5760\tx6480\tx7200\tx7920\tx8640\pardeftab720\pardirnatural\partightenfactor0
 \cf0 \kerning1\expnd0\expndtw0 {{\NeXTGraphic A944EB40-AEFD-4CA1-BF10-E8F52835CA8C.png \width18360 \height4420
diff --git a/ServiceRegistration/.gitignore b/ServiceRegistration/.gitignore
deleted file mode 100644
index d041cfb..0000000
--- a/ServiceRegistration/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-.depfile*
diff --git a/ServiceRegistration/adv-ctl-server.c b/ServiceRegistration/adv-ctl-server.c
index b5d72f1..7a91cce 100644
--- a/ServiceRegistration/adv-ctl-server.c
+++ b/ServiceRegistration/adv-ctl-server.c
@@ -1,6 +1,6 @@
 /* adv-ctl-proxy.c
  *
- * Copyright (c) 2019-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2019-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -52,11 +52,19 @@
 #include "thread-service.h"
 #include "omr-watcher.h"
 #include "omr-publisher.h"
+#include "dnssd-client.h"
+#include "service-publisher.h"
+#include "thread-tracker.h"
+#include "service-tracker.h"
+#include "route-tracker.h"
+#include "ifpermit.h"
 
 #include "cti-proto.h"
 #include "adv-ctl-common.h"
 #include "advertising_proxy_services.h"
 
+static void srp_xpc_client_finalize(srp_wanted_state_t *wanted);
+
 
 static int
 adv_ctl_block_service(bool enable, void *context)
@@ -129,7 +137,7 @@
 
 #if STUB_ROUTER
     if (server_state->stub_router_enabled) {
-        partition_discontinue_srp_service(server_state->route_state);
+        partition_discontinue_all_srp_service(server_state->route_state);
     } else
 #endif
     {
@@ -219,6 +227,145 @@
     server_state->break_srpl_time = true;
 }
 
+#if STUB_ROUTER || THREAD_DEVICE
+static void
+adv_ctl_thread_shutdown_continue(void *context)
+{
+    srp_server_t *server_state = context;
+
+    if (server_state->shutdown_timeout != NULL) {
+        ioloop_cancel_wake_event(server_state->shutdown_timeout);
+        ioloop_wakeup_release(server_state->shutdown_timeout);
+        server_state->shutdown_timeout = NULL;
+    }
+
+    xpc_object_t response;
+    response = xpc_dictionary_create_reply(server_state->shutdown_request);
+    if (response == NULL) {
+        ERROR("adv_xpc_message: Unable to create reply dictionary.");
+        return;
+    }
+    xpc_dictionary_set_uint64(response, kDNSSDAdvertisingProxyResponseStatus, 0);
+    xpc_connection_send_message(server_state->shutdown_connection, response);
+    xpc_release(response);
+    xpc_release(server_state->shutdown_request);
+    server_state->shutdown_request = NULL;
+    xpc_release(server_state->shutdown_connection);
+    server_state->shutdown_connection = NULL;
+
+    server_state->awaiting_service_removal = false;
+    server_state->awaiting_prefix_removal = false;
+
+    if (server_state->wanted != NULL) {
+        INFO("clearing server_state->wanted (%p) unconditionally.", server_state->wanted);
+        srp_xpc_client_finalize(server_state->wanted);
+    }
+}
+
+static void
+adv_ctl_start_thread_shutdown_timer(srp_server_t *server_state)
+{
+    if (server_state->shutdown_timeout == NULL) {
+        server_state->shutdown_timeout = ioloop_wakeup_create();
+    }
+    // If we can't allocate the shutdown timer, send the response immediately (which also probably won't
+    // work, oh well).
+    if (server_state->shutdown_timeout == NULL) {
+        adv_ctl_thread_shutdown_continue(server_state);
+        return;
+    }
+    // Wait no longer than two seconds for thread network data update
+    ioloop_add_wake_event(server_state->shutdown_timeout,
+                          server_state, adv_ctl_thread_shutdown_continue, NULL, 2 * IOLOOP_SECOND);
+}
+
+static bool
+adv_ctl_start_thread_shutdown(xpc_object_t request, xpc_connection_t connection, void *context)
+{
+    srp_server_t *server_state = context;
+
+    server_state->shutdown_connection = connection;
+    xpc_retain(server_state->shutdown_connection);
+    server_state->shutdown_request = request;
+    xpc_retain(server_state->shutdown_request);
+    if (0) {
+#if STUB_ROUTER
+    } else if (server_state->stub_router_enabled) {
+        if (server_state->service_tracker != NULL &&
+            service_tracker_local_service_seen(server_state->service_tracker))
+        {
+            service_tracker_cancel_probes(server_state->service_tracker); // This is a no-op for now.
+            server_state->awaiting_service_removal = true;
+        }
+        if (server_state->route_state->omr_publisher != NULL &&
+            omr_publisher_publishing_prefix(server_state->route_state->omr_publisher))
+        {
+            omr_publisher_unpublish_prefix(server_state->route_state->omr_publisher);
+            if (server_state->route_state->omr_watcher != NULL) {
+                server_state->awaiting_prefix_removal = true;
+            }
+        }
+        if (route_tracker_local_routes_seen(server_state->route_state->route_tracker)) {
+            nat64_thread_shutdown(server_state->route_state);
+            route_tracker_shutdown(server_state->route_state);
+            server_state->awaiting_route_removal = true;
+        }
+        srpl_shutdown(server_state);
+        partition_discontinue_all_srp_service(server_state->route_state);
+        adv_ctl_start_thread_shutdown_timer(server_state);
+        adv_ctl_thread_shutdown_status_check(server_state);
+#endif
+#if THREAD_DEVICE
+    } else {
+        if (server_state->service_tracker != NULL) {
+            service_tracker_cancel_probes(server_state->service_tracker);
+        }
+        if (server_state->dnssd_client != NULL) {
+            dnssd_client_cancel(server_state->dnssd_client);
+            dnssd_client_release(server_state->dnssd_client);
+            server_state->dnssd_client = NULL;
+        }
+        if (server_state->service_publisher != NULL) {
+            service_publisher_stop_publishing(server_state->service_publisher);
+            service_publisher_cancel(server_state->service_publisher);
+            service_publisher_release(server_state->service_publisher);
+            server_state->service_publisher = NULL;
+            if (server_state->service_tracker != NULL &&
+                service_tracker_local_service_seen(server_state->service_tracker))
+            {
+                server_state->awaiting_service_removal = true;
+            }
+        }
+        adv_ctl_thread_shutdown_status_check(server_state);
+        adv_ctl_start_thread_shutdown_timer(server_state);
+#endif
+    }
+    return true;
+}
+
+void
+adv_ctl_thread_shutdown_status_check(srp_server_t *server_state)
+{
+    if (0) {
+#if STUB_ROUTER
+    } else if (server_state->stub_router_enabled) {
+        if (!server_state->awaiting_prefix_removal &&
+            !server_state->awaiting_service_removal &&
+            !server_state->awaiting_route_removal)
+        {
+            adv_ctl_thread_shutdown_continue(server_state);
+        }
+#endif
+#if THREAD_DEVICE
+    } else {
+        if (!server_state->awaiting_service_removal) {
+            adv_ctl_thread_shutdown_continue(server_state);
+        }
+#endif
+    }
+}
+#endif // STUB_ROUTER || THREAD_DEVICE
+
 static int
 adv_ctl_block_anycast_service(bool block, void *context)
 {
@@ -327,7 +474,6 @@
 }
 
 
-
 static void
 adv_ctl_fd_finalize(void *context)
 {
@@ -598,6 +744,11 @@
         adv_ctl_start_breaking_time(connection->context);
         break;
 
+    case kDNSSDAdvertisingProxyStartThreadShutdown:
+        INFO("Client uid %d pid %d sent a kDNSSDAdvertisingProxyStartThreadShutdown request.",
+             connection->uid, connection->pid);
+        return adv_ctl_start_thread_shutdown(request, connection, context);
+
     case kDNSSDAdvertisingProxySetVariable:
         void *data = NULL;
         uint16_t data_len;
diff --git a/ServiceRegistration/adv-ctl-server.h b/ServiceRegistration/adv-ctl-server.h
index 8b6fe20..6d7f227 100644
--- a/ServiceRegistration/adv-ctl-server.h
+++ b/ServiceRegistration/adv-ctl-server.h
@@ -21,7 +21,11 @@
 
 #ifndef __ADV_CTL_SERVER_H__
 #define __ADV_CTL_SERVER_H__
+typedef struct missing_service missing_service_t;
+
+void adv_ctl_thread_shutdown_status_check(srp_server_t *NONNULL server_state);
 bool adv_ctl_init(void *NULLABLE context);
+bool adv_ctl_service_types_compare(const char *NONNULL service_type, const char *NONNULL registered_type);
 #endif /* __ADV_CTL_SERVER_H__ */
 
 // Local Variables:
diff --git a/ServiceRegistration/adv-resolve.c b/ServiceRegistration/adv-resolve.c
new file mode 100644
index 0000000..ee7f341
--- /dev/null
+++ b/ServiceRegistration/adv-resolve.c
@@ -0,0 +1,654 @@
+/* adv-resolve.c
+ *
+ * Copyright (c) 2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains the implementation of advertising proxy resolve
+ * API, which allows applications that use DNSServiceResolve to signal to
+ * the advertising proxy (as opposed to mDNSResponder) which services the
+ * application is trying to resolve.
+ */
+
+#include <netdb.h>
+#include <dns_sd.h>
+#include <os/log.h>
+
+#include "srp.h"
+#include "advertising_proxy_services.h"
+
+#undef ERROR
+#define ERROR(fmt, ...) os_log_error(adv_log, "%{public}s: " # fmt, __FUNCTION__, ##__VA_ARGS__)
+#undef INFO
+#define INFO(fmt, ...) os_log_info(adv_log, "%{public}s: " # fmt, __FUNCTION__, ##__VA_ARGS__)
+#undef FAULT
+#define FAULT(fmt, ...) os_log_info(adv_log, "%{public}s: " # fmt, __FUNCTION__, ##__VA_ARGS__)
+
+#define DECLARE_OBJECT_TYPE(x) int x##_created, x##_finalized, old_##x##_created, old_##x##_finalized
+DECLARE_OBJECT_TYPE(adv_instance_state);
+DECLARE_OBJECT_TYPE(adv_service_state);
+DECLARE_OBJECT_TYPE(adv_resolver_state);
+DECLARE_OBJECT_TYPE(advertising_proxy_subscription);
+
+// Discovery domains seen
+typedef struct discovery_domain discovery_domain_t;
+typedef struct adv_instance_state adv_instance_state_t;
+typedef struct adv_service_state adv_service_state_t;
+typedef struct adv_resolver_state adv_resolver_state_t;
+
+os_log_t advertising_proxy_global_os_log;
+#define adv_log advertising_proxy_global_os_log
+
+struct advertising_proxy_subscription {
+    int ref_count;
+    advertising_proxy_resolve_reply instance_callback;
+    advertising_proxy_browse_reply service_callback;
+    adv_instance_state_t *local_instance, *push_instance;
+    adv_service_state_t *service;
+    void *context;
+};
+
+static void
+advertising_proxy_subscription_finalize(advertising_proxy_subscription_t *subscription)
+{
+    if (subscription->local_instance != NULL) {
+        FAULT("subscription->local_instance is not NULL (%p)", subscription->local_instance);
+    }
+    if (subscription->push_instance != NULL) {
+        FAULT("subscription->push_instance is not NULL (%p)", subscription->push_instance);
+    }
+    if (subscription->service != NULL) {
+        FAULT("subscription->service is not NULL (%p)", subscription->service);
+    }
+}
+
+// Retained state relating to queries for services of a particular type.
+struct adv_service_state {
+    int ref_count;
+    adv_service_state_t *next;
+    char *service_type;
+    DNSServiceRef null_browse_ref; // default domains
+    DNSServiceRef push_browse_ref;  // default.service.arpa
+    adv_instance_state_t *instances;
+    size_t max_subscribers;
+    advertising_proxy_subscription_t **subscribers;
+};
+
+static void
+adv_service_state_finalize(adv_service_state_t *state)
+{
+    free(state->service_type);
+    if (state->null_browse_ref != NULL) {
+        FAULT("state->null_browse_ref is non-null: %p", state->null_browse_ref);
+        DNSServiceRefDeallocate(state->null_browse_ref);
+    }
+    if (state->push_browse_ref != NULL) {
+        FAULT("state->null_browse_ref is non-null: %p", state->push_browse_ref);
+        DNSServiceRefDeallocate(state->push_browse_ref);
+    }
+    if (state->instances != NULL) {
+        FAULT("state->instances not NULL (%p)", state->instances);
+    }
+    if (state->next != NULL) {
+        FAULT("state->next not NULL (%p)", state->next);
+    }
+    for (size_t i = 0; i < state->max_subscribers; i++) {
+        if (state->subscribers[i] != NULL) {
+            RELEASE_HERE(state->subscribers[i], advertising_proxy_subscription);
+        }
+    }
+    free(state->subscribers);
+}
+
+static void
+adv_service_state_cancel(adv_service_state_t *state)
+{
+    if (state->push_browse_ref != NULL) {
+        DNSServiceRefDeallocate(state->push_browse_ref);
+        state->push_browse_ref = NULL;
+        RELEASE_HERE(state, adv_service_state); // No more callbacks possible.
+    }
+    if (state->null_browse_ref != NULL) {
+        DNSServiceRefDeallocate(state->null_browse_ref);
+        state->null_browse_ref = NULL;
+        RELEASE_HERE(state, adv_service_state); // No more callbacks possible
+    }
+}
+
+// State relating to an instance of a particular service.
+struct adv_instance_state {
+    int ref_count;
+    adv_instance_state_t *next, *nsn; // nsn == next same name
+    char *name;
+    char *service_type;
+    char *domain;
+    DNSServiceRef resolve_ref;
+#define INITIAL_MAX_SUBSCRIBERS 10 // Should usually be all we need
+    size_t max_subscribers;
+    advertising_proxy_subscription_t **subscribers;
+};
+
+static void
+adv_instance_state_finalize(adv_instance_state_t *state)
+{
+    free(state->name);
+    free(state->service_type);
+    free(state->domain);
+    if (state->resolve_ref != NULL) {
+        FAULT("state->resolve_ref is non-null: %p", state->resolve_ref);
+        DNSServiceRefDeallocate(state->resolve_ref); // shouldn't be possible
+    }
+    for (size_t i = 0; i < state->max_subscribers; i++) {
+        if (state->subscribers[i] != NULL) {
+            RELEASE_HERE(state->subscribers[i], advertising_proxy_subscription);
+        }
+    }
+    free(state->subscribers);
+    if (state->nsn != NULL) {
+        FAULT("state->nsn is not NULL (%p)", state->nsn);
+    }
+    if (state->next != NULL) {
+        FAULT("state->next is not NULL (%p)", state->next);
+    }
+}
+
+static void
+adv_instance_state_cancel(adv_instance_state_t *state)
+{
+    if (state->resolve_ref != NULL) {
+        DNSServiceRefDeallocate(state->resolve_ref);
+        state->resolve_ref = NULL;
+        RELEASE_HERE(state, adv_instance_state); // No longer possible to get a callback
+    }
+}
+
+// Call on startup to initialize the advertising proxy resolver function
+advertising_proxy_error_type
+advertising_proxy_resolver_init(os_log_t log_thingy)
+{
+    if (log_thingy != NULL) {
+        adv_log = log_thingy;
+    } else {
+        adv_log = OS_LOG_DEFAULT;
+    }
+
+    // Any additional per-run setup...
+    return kDNSSDAdvertisingProxyStatus_NoError;
+}
+
+static adv_service_state_t *
+adv_service_state_create(const char *regtype)
+{
+    adv_service_state_t *state = calloc(1, sizeof(*state));
+    if (state == NULL) {
+        ERROR("no memory for service state %{public}s", regtype);
+    }
+    RETAIN_HERE(state, adv_service_state);
+    state->service_type = strdup(regtype);
+    if (state->service_type == NULL) {
+        RELEASE_HERE(state, adv_service_state);
+        state = NULL;
+        ERROR("no memory for service_type %{public}s", regtype);
+    }
+    return state;
+}
+
+static adv_instance_state_t *
+adv_instance_state_create(const char *name, const char *regtype, const char *domain)
+{
+    adv_instance_state_t *ret = NULL, *state = calloc(1, sizeof(*state));
+    if (state == NULL) {
+        ERROR("no memory for state %{private}s . %{public}s", name, regtype);
+    } else {
+        RETAIN_HERE(state, adv_instance_state);
+        state->name = strdup(name);
+        if (state->name == NULL) {
+            ERROR("no memory for name %{private}s . %{public}s", name, regtype);
+            goto out;
+        }
+        state->service_type = strdup(regtype);
+        if (state->service_type == NULL) {
+            ERROR("no memory for service type %{private}s . %{public}s", name, regtype);
+            goto out;
+        }
+        if (domain != NULL) {
+            state->domain = strdup(domain);
+            if (state->domain == NULL) {
+                ERROR("no memory for domain %{private}s . %{public}s", name, domain);
+                goto out;
+            }
+        }
+        ret = state;
+        state = NULL;
+    }
+out:
+    if (state != NULL) {
+        RELEASE_HERE(state, adv_instance_state);
+    }
+    return ret;
+}
+
+static advertising_proxy_subscription_t *
+advertising_proxy_subscription_create(void)
+{
+    advertising_proxy_subscription_t *subscription = calloc(1, sizeof(*subscription));
+    if (subscription == NULL) {
+        goto out;
+    }
+    RETAIN_HERE(subscription, advertising_proxy_subscription);
+out:
+    return subscription;
+}
+
+
+static void
+adv_instance_unsubscribe(adv_instance_state_t *instance, advertising_proxy_subscription_t *subscription)
+{
+    if (instance->resolve_ref != NULL) {
+        DNSServiceRefDeallocate(instance->resolve_ref);
+        instance->resolve_ref = NULL;
+    }
+    for (size_t i = 0; i < instance->max_subscribers; i++) {
+        if (instance->subscribers[i] == subscription) {
+            RELEASE_HERE(instance->subscribers[i], advertising_proxy_subscription);
+            instance->subscribers[i] = NULL;
+            break;
+        }
+    }
+}
+
+static void
+adv_service_unsubscribe(adv_service_state_t *service, advertising_proxy_subscription_t *subscription)
+{
+    if (service->null_browse_ref != NULL) {
+        DNSServiceRefDeallocate(service->null_browse_ref);
+        service->null_browse_ref = NULL;
+    }
+    if (service->push_browse_ref != NULL) {
+        DNSServiceRefDeallocate(service->push_browse_ref);
+        service->push_browse_ref = NULL;
+    }
+    for (size_t i = 0; i < service->max_subscribers; i++) {
+        if (service->subscribers[i] == subscription) {
+            RELEASE_HERE(service->subscribers[i], advertising_proxy_subscription);
+            service->subscribers[i] = NULL;
+            break;
+        }
+    }
+}
+
+advertising_proxy_error_type
+advertising_proxy_subscription_retain_(advertising_proxy_subscription_t *subscription, const char *file, int line)
+{
+    RETAIN(subscription, advertising_proxy_subscription);
+    return kDNSSDAdvertisingProxyStatus_NoError;
+}
+
+advertising_proxy_error_type
+advertising_proxy_subscription_release_(advertising_proxy_subscription_t *subscription, const char *file, int line)
+{
+    RELEASE(subscription, advertising_proxy_subscription);
+    return kDNSSDAdvertisingProxyStatus_NoError;
+}
+
+advertising_proxy_error_type
+advertising_proxy_subscription_cancel(advertising_proxy_subscription_t *subscription)
+{
+    if (subscription->local_instance) {
+        adv_instance_unsubscribe(subscription->local_instance, subscription);
+        RELEASE_HERE(subscription->local_instance, adv_instance_state);
+        subscription->local_instance = NULL;
+    }
+    if (subscription->push_instance) {
+        adv_instance_unsubscribe(subscription->push_instance, subscription);
+        RELEASE_HERE(subscription->push_instance, adv_instance_state);
+        subscription->push_instance = NULL;
+    }
+    if (subscription->service) {
+        adv_service_unsubscribe(subscription->service, subscription);
+        RELEASE_HERE(subscription->service, adv_service_state);
+        subscription->service = NULL;
+    }
+    return kDNSSDAdvertisingProxyStatus_NoError;
+}
+
+advertising_proxy_error_type
+advertising_proxy_registrar_create(advertising_proxy_subscription_t **subscription_ret, run_context_t clientq,
+                                   advertising_proxy_registrar_reply callback, void *context)
+{
+    run_context_t queue = clientq;
+    advertising_proxy_error_type result = kDNSSDAdvertisingProxyStatus_NoError;
+    advertising_proxy_subscription_t *subscription = NULL;
+    // Sanity check arguments.
+    if (subscription_ret == NULL || callback == NULL) {
+        result = kDNSSDAdvertisingProxyStatus_Invalid;
+        goto out;
+    }
+    if (queue == NULL) {
+        queue = dispatch_get_main_queue();
+    }
+
+    subscription = advertising_proxy_subscription_create();
+
+    if (subscription == NULL) {
+        result = kDNSSDAdvertisingProxyStatus_NoMemory;
+        goto out;
+    }
+    advertising_proxy_subscription_t *tmp = subscription;
+    RETAIN_HERE(subscription, advertising_proxy_subscription); // For callback
+    dispatch_async(clientq, ^{
+        callback(tmp, kDNSSDAdvertisingProxyStatus_NoError, context);
+        RELEASE_HERE(subscription, advertising_proxy_subscription); // For callback
+    });
+    *subscription_ret = subscription;
+    subscription = NULL;
+
+out:
+    if (subscription != NULL) {
+        RELEASE_HERE(subscription, advertising_proxy_subscription);
+    }
+    return result;
+}
+
+static void
+adv_subscriber_add(size_t *num_subscribers, advertising_proxy_subscription_t ***p_subscribers,
+                   advertising_proxy_subscription_t *subscriber)
+{
+    size_t max_subscribers = *num_subscribers;
+    advertising_proxy_subscription_t **subscribers = *p_subscribers;
+
+    for (size_t i = 0; i < max_subscribers; i++) {
+        if (subscribers[i] == NULL) {
+            subscribers[i] = subscriber;
+            RETAIN_HERE(subscribers[i], advertising_proxy_subscription);
+            return;
+        }
+    }
+    size_t new_max = subscribers == NULL ? INITIAL_MAX_SUBSCRIBERS : max_subscribers * 2;
+    advertising_proxy_subscription_t **new_subscribers = calloc(new_max, sizeof(*subscribers));
+    if (new_subscribers == NULL) {
+        ERROR("no memory for %zu subscribers", new_max);
+        return;
+    }
+    if (*p_subscribers != NULL) {
+        memcpy(new_subscribers, subscribers, max_subscribers * sizeof(*subscribers));
+        free(*p_subscribers);
+    }
+    *p_subscribers = new_subscribers;
+    new_subscribers[max_subscribers] = subscriber;
+    RETAIN_HERE(new_subscribers[max_subscribers], advertising_proxy_subscription);
+    *num_subscribers = new_max;
+}
+
+static void
+adv_service_state_subscriber_add(adv_service_state_t *service, advertising_proxy_subscription_t *subscriber)
+{
+    adv_subscriber_add(&service->max_subscribers, &service->subscribers, subscriber);
+}
+
+static void
+advertising_proxy_browse_callback(DNSServiceRef UNUSED sdRef, DNSServiceFlags flags, uint32_t interface_index,
+                                  DNSServiceErrorType error_code,
+                                  const char *instance_name, const char *service_type, const char *UNUSED domain, void *context)
+{
+    adv_service_state_t *service = context;
+    advertising_proxy_error_type error = kDNSSDAdvertisingProxyStatus_NoError;
+
+    RETAIN_HERE(service, adv_service_state);
+    if (error_code != kDNSServiceErr_NoError) {
+        error = kDNSSDAdvertisingProxyStatus_UnknownErr;
+    }
+    for (size_t i = 0; i < service->max_subscribers; i++) {
+        advertising_proxy_subscription_t *subscription = service->subscribers[i];
+        if (subscription != NULL) {
+            subscription->service_callback(subscription, error, interface_index,
+                                           (flags & kDNSServiceFlagsAdd) ? true : false,
+                                           instance_name, service_type, context);
+        }
+    }
+    if (error != kDNSSDAdvertisingProxyStatus_NoError) {
+        adv_service_state_cancel(service);
+    }
+    RELEASE_HERE(service, adv_service_state);
+}
+
+advertising_proxy_error_type
+advertising_proxy_browse_create(advertising_proxy_subscription_t **subscription_ret, run_context_t clientq,
+                                const char *regtype, advertising_proxy_browse_reply callback, void *context)
+{
+    advertising_proxy_error_type status = kDNSSDAdvertisingProxyStatus_NoError;
+    advertising_proxy_subscription_t *subscription = NULL;
+    adv_service_state_t *service = NULL;
+
+    // Sanity check arguments.
+    if (subscription_ret == NULL || regtype == NULL || callback == NULL) {
+        status = kDNSSDAdvertisingProxyStatus_Invalid;
+        goto out;
+    }
+
+    run_context_t queue = clientq;
+    if (queue == NULL) {
+        queue = dispatch_get_main_queue();
+    }
+
+    subscription = advertising_proxy_subscription_create();
+    if (subscription == NULL) {
+        status = kDNSSDAdvertisingProxyStatus_NoMemory;
+        goto out;
+    }
+    service = adv_service_state_create(regtype);
+    if (service == NULL) {
+        status = kDNSSDAdvertisingProxyStatus_NoMemory;
+        goto out;
+    }
+    adv_service_state_subscriber_add(service, subscription);
+    subscription->service = service;
+    RETAIN_HERE(subscription->service, adv_service_state);
+
+    subscription->service_callback = callback;
+    subscription->context = context;
+
+    DNSServiceErrorType error = DNSServiceBrowse(&service->null_browse_ref, 0, kDNSServiceInterfaceIndexAny,
+                                                 regtype, NULL, advertising_proxy_browse_callback, service);
+    if (error == kDNSServiceErr_NoError) {
+        error = DNSServiceSetDispatchQueue(service->null_browse_ref, queue);
+    }
+    if (error != kDNSServiceErr_NoError) {
+        ERROR("browse for service %{public}s in the default domains failed with %d", regtype, error);
+        status = kDNSSDAdvertisingProxyStatus_UnknownErr;
+        goto out;
+    }
+    RETAIN_HERE(service, adv_service_state); // For null_browse callback
+
+    error = DNSServiceBrowse(&service->push_browse_ref, 0, kDNSServiceInterfaceIndexAny,
+                             regtype, "default.service.arpa", advertising_proxy_browse_callback, service);
+    if (error == kDNSServiceErr_NoError) {
+        error = DNSServiceSetDispatchQueue(service->push_browse_ref, queue);
+    }
+    if (error != kDNSServiceErr_NoError) {
+        ERROR("browse on service %{public}s in the push domain failed with %d", regtype, error);
+        status = kDNSSDAdvertisingProxyStatus_UnknownErr;
+        goto out;
+    }
+    RETAIN_HERE(service, adv_service_state); // For push_browse callback
+    *subscription_ret = subscription;
+    subscription = NULL;
+    service = NULL;
+
+out:
+    if (subscription != NULL) {
+        advertising_proxy_subscription_cancel(subscription);
+        RELEASE_HERE(subscription, advertising_proxy_subscription);
+    }
+    if (service != NULL) {
+        adv_service_state_cancel(service);
+        RELEASE_HERE(service, adv_service_state);
+    }
+    return status;
+}
+
+static void
+adv_instance_state_subscriber_add(adv_instance_state_t *instance, advertising_proxy_subscription_t *subscriber)
+{
+    adv_subscriber_add(&instance->max_subscribers, &instance->subscribers, subscriber);
+}
+
+static void
+advertising_proxy_resolve_callback(DNSServiceRef UNUSED sdRef, DNSServiceFlags flags, uint32_t interface_index,
+                                   DNSServiceErrorType error_code, const char *fullname, const char *hosttarget,
+                                   uint16_t port, uint16_t txt_length, const unsigned char *txt_record, void *context)
+{
+    adv_instance_state_t *instance = context;
+    advertising_proxy_error_type error = kDNSSDAdvertisingProxyStatus_NoError;
+
+    RELEASE_HERE(instance, adv_instance_state);
+    if (error_code != kDNSServiceErr_NoError) {
+        error = kDNSSDAdvertisingProxyStatus_UnknownErr;
+    }
+    for (size_t i = 0; i < instance->max_subscribers; i++) {
+        advertising_proxy_subscription_t *subscription = instance->subscribers[i];
+        if (subscription != NULL) {
+            subscription->instance_callback(subscription, error, interface_index,
+                                            (flags & kDNSServiceFlagsAdd) ? true : true, fullname, hosttarget, port,
+                                            txt_length, txt_record, context);
+        }
+    }
+    if (error != kDNSSDAdvertisingProxyStatus_NoError) {
+        adv_instance_state_cancel(instance);
+    }
+    RELEASE_HERE(instance, adv_instance_state);
+}
+
+advertising_proxy_error_type
+advertising_proxy_resolve_create(advertising_proxy_subscription_t **subscription_ret, run_context_t clientq,
+                                 const char *name, const char *regtype, const char *domain,
+                                 advertising_proxy_resolve_reply callback, void *context)
+{
+    advertising_proxy_error_type status = kDNSSDAdvertisingProxyStatus_NoError;
+    advertising_proxy_subscription_t *subscription = NULL;
+    adv_instance_state_t *local_instance = NULL;
+    adv_instance_state_t *push_instance = NULL;
+
+    // Sanity check arguments.
+    if (subscription_ret == NULL || name == NULL || regtype == NULL || callback == NULL) {
+        status = kDNSSDAdvertisingProxyStatus_Invalid;
+        goto out;
+    }
+    run_context_t queue = clientq;
+    if (queue == NULL) {
+        queue = dispatch_get_main_queue();
+    }
+    subscription = advertising_proxy_subscription_create();
+    if (subscription == NULL) {
+        status = kDNSSDAdvertisingProxyStatus_NoMemory;
+        goto out;
+    }
+    local_instance = adv_instance_state_create(name, regtype, "local");
+    if (local_instance == NULL) {
+        status = kDNSSDAdvertisingProxyStatus_NoMemory;
+        goto out;
+    }
+    adv_instance_state_subscriber_add(local_instance, subscription);
+    subscription->local_instance = local_instance;
+    RETAIN_HERE(subscription->local_instance, adv_instance_state);
+
+    subscription->instance_callback = callback;
+    subscription->context = context;
+
+    DNSServiceErrorType error = DNSServiceResolve(&local_instance->resolve_ref, 0, kDNSServiceInterfaceIndexAny,
+                                                  name, regtype, domain == NULL ? "local" : domain,
+                                                  advertising_proxy_resolve_callback, local_instance);
+    if (error == kDNSServiceErr_NoError) {
+        error = DNSServiceSetDispatchQueue(local_instance->resolve_ref, queue);
+        if (error != kDNSServiceErr_NoError) {
+            status = kDNSSDAdvertisingProxyStatus_UnknownErr;
+            goto out;
+        }
+    }
+    if (error != kDNSServiceErr_NoError) {
+        ERROR("resolve for %{private}s on service %{public}s in the default domains failed with %d",
+              name, regtype, error);
+        status = kDNSSDAdvertisingProxyStatus_UnknownErr;
+        goto out;
+    }
+    RETAIN_HERE(local_instance, adv_instance_state); // for callback
+
+    if (domain == NULL) {
+        push_instance = adv_instance_state_create(name, regtype, "default.service.arpa");
+        if (push_instance == NULL) {
+            status = kDNSSDAdvertisingProxyStatus_NoMemory;
+            goto out;
+        }
+        adv_instance_state_subscriber_add(push_instance, subscription);
+        subscription->push_instance = push_instance;
+        RETAIN_HERE(subscription->push_instance, adv_instance_state);
+
+        error = DNSServiceResolve(&push_instance->resolve_ref, 0, kDNSServiceInterfaceIndexAny,
+                                  name, regtype, "default.service.arpa",
+                                  advertising_proxy_resolve_callback, push_instance);
+        if (error == kDNSServiceErr_NoError) {
+            error = DNSServiceSetDispatchQueue(push_instance->resolve_ref, queue);
+            if (error != kDNSServiceErr_NoError) {
+                status = kDNSSDAdvertisingProxyStatus_UnknownErr;
+                goto out;
+            }
+            RETAIN_HERE(push_instance, adv_instance_state); // for callback
+        }
+    }
+
+    if (error != kDNSServiceErr_NoError) {
+        ERROR("resolve for %{private}s on service %{public}s in the push domain failed with %d", name, regtype, error);
+        status = kDNSSDAdvertisingProxyStatus_UnknownErr;
+        goto out;
+    }
+    *subscription_ret = subscription;
+    subscription = NULL;
+    push_instance = NULL;
+    local_instance = NULL;
+
+out:
+    if (push_instance != NULL) {
+        adv_instance_state_cancel(push_instance);
+        RELEASE_HERE(push_instance, adv_instance_state);
+    }
+    if (local_instance != NULL) {
+        adv_instance_state_cancel(local_instance);
+        RELEASE_HERE(local_instance, adv_instance_state);
+    }
+    if (subscription != NULL) {
+        advertising_proxy_subscription_cancel(subscription);
+        RELEASE_HERE(subscription, advertising_proxy_subscription);
+    }
+    return status;
+}
+
+advertising_proxy_error_type
+advertising_proxy_get_addresses(advertising_proxy_subscription_t **subscription_ret, run_context_t clientq,
+                                const char *name, advertising_proxy_address_reply callback, void *context)
+{
+    (void)subscription_ret;
+    (void)clientq;
+    (void)name;
+    (void)callback;
+    (void)context;
+    return kDNSSDAdvertisingProxyStatus_NoError;
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/advertising_proxy_services.c b/ServiceRegistration/advertising_proxy_services.c
index bafc5e5..d6b30a5 100644
--- a/ServiceRegistration/advertising_proxy_services.c
+++ b/ServiceRegistration/advertising_proxy_services.c
@@ -1,6 +1,6 @@
 /* advertising_proxy_services.h
  *
- * Copyright (c) 2020-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2020-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -41,6 +41,7 @@
 int adv_host_finalized;
 int advertising_proxy_conn_ref_created;
 int advertising_proxy_conn_ref_finalized;
+os_log_t global_os_log;
 #endif
 
 static void
@@ -536,17 +537,27 @@
     return errx;
 }
 
-static void
-adv_set_variable_callback(advertising_proxy_conn_ref conn_ref, xpc_object_t *response, int status)
+advertising_proxy_error_type
+advertising_proxy_start_thread_shutdown(advertising_proxy_conn_ref *conn_ref,
+                                        run_context_t client_queue, advertising_proxy_reply callback)
 {
-    if (conn_ref->app_context_callback != NULL) {
-        conn_ref->app_context_callback(conn_ref, conn_ref->context, response, status);
+    advertising_proxy_error_type errx;
+    errx = adv_send_command(conn_ref, client_queue, "advertising_proxy_start_thread_shutdown",
+                            kDNSSDAdvertisingProxyStartThreadShutdown, callback, NULL, 0);
+    return errx;
+}
+
+static void
+adv_set_variable_callback(advertising_proxy_conn_ref conn_ref, xpc_object_t *response, advertising_proxy_error_type status)
+{
+    if (conn_ref->app_response_callback != NULL) {
+        conn_ref->app_response_callback(conn_ref, conn_ref->context, response, status);
     }
 }
 
 advertising_proxy_error_type
 advertising_proxy_set_variable(advertising_proxy_conn_ref *conn_ref,
-                               run_context_t client_queue, advertising_proxy_context_reply callback, void *context,
+                               run_context_t client_queue, advertising_proxy_response_reply callback, void *context,
                                const char *name, const char *value)
 {
     advertising_proxy_error_type errx;
@@ -564,7 +575,7 @@
     free(buf);
     if (errx == kDNSSDAdvertisingProxyStatus_NoError) {
         (*conn_ref)->context = context;
-        (*conn_ref)->app_context_callback = callback;
+        (*conn_ref)->app_response_callback = callback;
     }
     return errx;
 }
diff --git a/ServiceRegistration/advertising_proxy_services.h b/ServiceRegistration/advertising_proxy_services.h
index 12eff09..2b149e5 100644
--- a/ServiceRegistration/advertising_proxy_services.h
+++ b/ServiceRegistration/advertising_proxy_services.h
@@ -1,6 +1,6 @@
 /* advertising_proxy_services.h
  *
- * Copyright (c) 2020-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2020-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -22,12 +22,25 @@
 #ifndef DNSSD_PROXY_SERVICES_H
 #define DNSSD_PROXY_SERVICES_H
 
+#if !defined(__BEGIN_DECLS)
+    #if defined(__cplusplus)
+        #define __BEGIN_DECLS               extern "C" {
+        #define __END_DECLS                     }
+    #else
+        #define __BEGIN_DECLS
+        #define __END_DECLS
+    #endif
+#endif
+
+__BEGIN_DECLS
 typedef void *run_context_t;
 typedef struct _cti_connection_t *advertising_proxy_conn_ref;
 #define ADV_CTL_SERVER_SOCKET_NAME "/var/run/adv-ctl-server-socket"
 #define RCHAR char
 #define RUCHAR uint8_t
 
+typedef struct advertising_proxy_subscription advertising_proxy_subscription_t;
+
 typedef struct advertising_proxy_host_address {
     uint16_t rrtype;
     RUCHAR *NULLABLE rdata;
@@ -74,6 +87,9 @@
     kDNSSDAdvertisingProxyStatus_NotPermitted              = -65571
 } advertising_proxy_error_type;
 
+// Register for notification that TLS key has changed
+#define kDNSSDAdvertisingProxyTLSKeyUpdateNotification "com.apple.srp-mdns-proxy.tls-key-update"
+
 /*********************************************************************************************
  *
  *  DNSSD Advertising Proxy control/management library functions
@@ -98,9 +114,9 @@
     advertising_proxy_error_type        errCode
 );
 
-/* advertising_proxy_context_reply: Callback from all DNSSD Advertising proxy library functions
+/* advertising_proxy_response_reply: Callback from all DNSSD Advertising proxy library functions
  *
- * advertising_proxy_context_reply() parameters:
+ * advertising_proxy_response_reply() parameters:
  *
  * conn_ref:                   The advertising_proxy_conn_ref initialized by the library function.  Call advertising_proxy_
  *
@@ -113,7 +129,7 @@
  *
  */
 
-typedef void (*advertising_proxy_context_reply)
+typedef void (*advertising_proxy_response_reply)
 (
     advertising_proxy_conn_ref  NULLABLE conn_ref,
     void *                      NULLABLE context;
@@ -122,6 +138,7 @@
 );
 
 
+
 /* advertising_proxy_flush_entries
  *
  * Flushes any host entries that have been registered with the advertising proxy.   For testing only:
@@ -803,6 +820,39 @@
     advertising_proxy_reply             NULLABLE callback
 );
 
+/* advertising_proxy_start_start_thread_shutdown
+ *
+ * For testing, start breaking SIG(0) validation on replicated host messages. This tests that we correctly
+ * handle such failures in the SRP replication protocol.
+ *
+ * advertising_proxy_start_dropping_push_connections() Parameters:
+ *
+ * conn_ref:                  A pointer to advertising_proxy_conn_ref that is initialized to NULL.
+ *                            If the call succeeds it will be initialized to a non-NULL value.
+ *                            The same conn_ref can be used for more than one call.
+ *
+ * clientq:                   Queue the client wants to schedule the callback on (Note: Must not be NULL)
+ *
+ * callback:                  Callback function for the client that indicates success or failure.
+ *                            Callback is not called until either the command has failed, or has completed.
+ *                            Completion in this case means that all Thread services and prefixes were
+ *                            successfully removed from the Thread network data and a network data update
+ *                            was seen with this information removed, or else two seconds passed without
+ *                            seeing the update (in which case we give up).
+ *
+ * return value:              Returns kDNSSDAdvertisingProxy_NoError when no error otherwise returns an
+ *                            error code indicating the error that occurred.
+ *
+ */
+
+DNS_SERVICES_EXPORT
+advertising_proxy_error_type advertising_proxy_start_thread_shutdown
+(
+    advertising_proxy_conn_ref NONNULL *NULLABLE conn_ref,
+    run_context_t                        NONNULL clientq,
+    advertising_proxy_reply             NULLABLE callback
+);
+
 /* advertising_proxy_set-variable
  *
  * Set the specified variable to the specified value on the advertising proxy
@@ -828,9 +878,8 @@
  *
  * return value:              Returns kDNSSDAdvertisingProxy_NoError when no error otherwise returns an
  *                            error code indicating the error that occurred. Note: A return value of
- *                            kDNSSDAdvertisingProxy_NoError does not mean that srp replication connections
- *                            were successfully dropped. The callback may asynchronously return an
- *                            error (such as kDNSSDAdvertisingProxy_DaemonNotRunning)
+ *                            kDNSSDAdvertisingProxy_NoError does not mean that the variable was set, just
+ *                            that the variable set command was successfully created and sent.
  *
  */
 
@@ -839,12 +888,13 @@
 (
     advertising_proxy_conn_ref NONNULL *NULLABLE conn_ref,
     run_context_t                        NONNULL clientq,
-    advertising_proxy_context_reply     NULLABLE callback,
+    advertising_proxy_response_reply    NULLABLE callback,
     void *                              NULLABLE context,
     const char *                         NONNULL name,
     const char *                         NONNULL value
 );
 
+
 /* advertising_proxy_ref_dealloc()
  *
  * Terminate a connection with the daemon and free memory associated with the advertising_proxy_conn_ref.
@@ -855,8 +905,43 @@
  * conn_ref:        A advertising_proxy_conn_ref initialized by any of the advertising_proxy_*() calls.
  *
  */
-DNS_SERVICES_EXPORT
-void advertising_proxy_ref_dealloc(advertising_proxy_conn_ref NONNULL conn_ref);
+DNS_SERVICES_EXPORT void
+advertising_proxy_ref_dealloc(advertising_proxy_conn_ref NONNULL conn_ref);
+
+DNS_SERVICES_EXPORT advertising_proxy_error_type
+advertising_proxy_resolver_init(os_log_t NULLABLE log_thingy);
+
+DNS_SERVICES_EXPORT advertising_proxy_error_type
+advertising_proxy_browse_create(advertising_proxy_subscription_t *NULLABLE *NONNULL aref,
+                                run_context_t NONNULL clientq, const char *NONNULL regtype,
+                                advertising_proxy_browse_reply NONNULL callBack, void *NULLABLE context);
+
+DNS_SERVICES_EXPORT advertising_proxy_error_type
+advertising_proxy_resolve_create(advertising_proxy_subscription_t *NULLABLE *NONNULL subscription_ret,
+                                 run_context_t NONNULL clientq,
+                                 const char *NONNULL name, const char *NONNULL regtype,
+                                 const char *NONNULL domain,
+                                 advertising_proxy_resolve_reply NONNULL callback, void *NULLABLE context);
+DNS_SERVICES_EXPORT advertising_proxy_error_type
+advertising_proxy_registrar_create(advertising_proxy_subscription_t *NULLABLE *NONNULL subscription_ret,
+                                   run_context_t NONNULL clientq,
+                                   advertising_proxy_registrar_reply NONNULL callback,
+                                   void *NULLABLE context);
+DNS_SERVICES_EXPORT advertising_proxy_error_type
+advertising_proxy_get_addresses(advertising_proxy_subscription_t *NONNULL *NULLABLE subscription_ret, run_context_t NONNULL clientq,
+                                const char *NULLABLE name, advertising_proxy_address_reply NONNULL callback, void *NULLABLEcontext);
+
+#define advertising_proxy_subscription_retain(subscription) advertising_proxy_subscription_retain_(subscription, __FILE__, __LINE__)
+DNS_SERVICES_EXPORT advertising_proxy_error_type
+advertising_proxy_subscription_retain_(advertising_proxy_subscription_t *NONNULL subscription, const char *NONNULL file, int line);
+
+#define advertising_proxy_subscription_release(subscription) advertising_proxy_subscription_release_(subscription, __FILE__, __LINE__)
+DNS_SERVICES_EXPORT advertising_proxy_error_type
+advertising_proxy_subscription_release_(advertising_proxy_subscription_t *NONNULL subscription, const char *NONNULL file, int line);
+
+DNS_SERVICES_EXPORT advertising_proxy_error_type
+advertising_proxy_subscription_cancel(advertising_proxy_subscription_t *NONNULL subscription);
+__END_DECLS
 #endif /* DNSSD_PROXY_SERVICES_H */
 
 // Local Variables:
diff --git a/ServiceRegistration/config-parse.c b/ServiceRegistration/config-parse.c
index 4864159..5739264 100644
--- a/ServiceRegistration/config-parse.c
+++ b/ServiceRegistration/config-parse.c
@@ -54,139 +54,139 @@
 // We parse the verb first, then that tells us how many hunks of text to expect.
 // Each hunk is space-delineated; the last hunk can contain spaces.
 static bool config_parse_line(void *context, const char *filename, char *line, int lineno,
-							  config_file_verb_t *verbs, int num_verbs)
+                              config_file_verb_t *verbs, int num_verbs)
 {
-	char *sp;
+    char *sp;
 #define MAXCFHUNKS 10
-	char *hunks[MAXCFHUNKS];
-	int num_hunks = 0;
-	config_file_verb_t *config_file_verb = NULL;
-	int i;
+    char *hunks[MAXCFHUNKS];
+    int num_hunks = 0;
+    config_file_verb_t *config_file_verb = NULL;
+    int i;
 
-	sp = line;
-	do {
-		// Skip leading spaces.
-		while (*sp && (*sp == ' ' || *sp == '\t'))
-			sp++;
-		if (num_hunks == 0) {
-			// If this is a blank line with spaces on it or a comment line, we ignore it.
-			if (!*sp || *sp == '#')
-				return true;
-		}
-		hunks[num_hunks++] = sp;
-		// Find EOL or hunk
-		while (*sp && (*sp != ' ' && *sp != '\t')) {
-			sp++;
-		}
-		if (*sp) {
-			*sp++ = 0;
-		}
-		if (num_hunks == 1) {
-			for (i = 0; i < num_verbs; i++) {
-				// If the verb name matches, or the verb name is NULL (meaning whatever doesn't
-				// match a preceding verb), we've found our verb.
-				if (verbs[i].name == NULL || !strcmp(verbs[i].name, hunks[0])) {
-					config_file_verb = &verbs[i];
-					break;
-				}
-			}
-			if (config_file_verb == NULL) {
-				INFO("unknown verb %s at line %d", hunks[0], lineno);
-				return false;
-			}
-		}				
-	} while (*sp && num_hunks < MAXCFHUNKS && config_file_verb->max_hunks > num_hunks);
-	
-	// If we didn't get the hunks we needed, bail.
-	if (config_file_verb->min_hunks > num_hunks) {
-		INFO("error: verb %s requires between %d and %d modifiers; %d given at line %d",
-			 hunks[0], config_file_verb->min_hunks, config_file_verb->max_hunks, num_hunks, lineno);
-		return false;
-	}
+    sp = line;
+    do {
+        // Skip leading spaces.
+        while (*sp && (*sp == ' ' || *sp == '\t'))
+            sp++;
+        if (num_hunks == 0) {
+            // If this is a blank line with spaces on it or a comment line, we ignore it.
+            if (!*sp || *sp == '#')
+                return true;
+        }
+        hunks[num_hunks++] = sp;
+        // Find EOL or hunk
+        while (*sp && (*sp != ' ' && *sp != '\t')) {
+            sp++;
+        }
+        if (*sp) {
+            *sp++ = 0;
+        }
+        if (num_hunks == 1) {
+            for (i = 0; i < num_verbs; i++) {
+                // If the verb name matches, or the verb name is NULL (meaning whatever doesn't
+                // match a preceding verb), we've found our verb.
+                if (verbs[i].name == NULL || !strcmp(verbs[i].name, hunks[0])) {
+                    config_file_verb = &verbs[i];
+                    break;
+                }
+            }
+            if (config_file_verb == NULL) {
+                INFO("unknown verb %s at line %d", hunks[0], lineno);
+                return false;
+            }
+        }
+    } while (*sp && num_hunks < MAXCFHUNKS && config_file_verb->max_hunks > num_hunks);
 
-	return config_file_verb->handler(context, filename, hunks, num_hunks, lineno);
+    // If we didn't get the hunks we needed, bail.
+    if (config_file_verb->min_hunks > num_hunks) {
+        INFO("error: verb %s requires between %d and %d modifiers; %d given at line %d",
+             hunks[0], config_file_verb->min_hunks, config_file_verb->max_hunks, num_hunks, lineno);
+        return false;
+    }
+
+    return config_file_verb->handler(context, filename, hunks, num_hunks, lineno);
 }
 
 // Parse a configuration file
 bool config_parse(void *context, const char *filename, config_file_verb_t *verbs, int num_verbs)
 {
-	int file;
-	char *buf, *line, *eof, *eol, *nextCR, *nextNL;
-	off_t flen, have;
+    int file;
+    char *buf, *line, *eof, *eol, *nextCR, *nextNL;
+    off_t flen;
     ssize_t len;
+    size_t have;
     int lineno;
-	bool success = true;
+    bool success = true;
 
-	file = open(filename, O_RDONLY);
-	if (file < 0) {
-		INFO("fatal: %s: %s", filename, strerror(errno));
-		return false;
-	}
+    file = open(filename, O_RDONLY);
+    if (file < 0) {
+        INFO("fatal: %s: %s", filename, strerror(errno));
+        return false;
+    }
 
-	// Get the length of the file.
-	flen = lseek(file, 0, SEEK_END);
-	lseek(file, 0, SEEK_SET);
-	buf = malloc(flen + 1);
-	if (buf == NULL) {
-		INFO("fatal: not enough memory for %s", filename);
-		goto outclose;
-	}
-	
-	// Just in case we have a read() syscall that doesn't always read the whole file at once
-	have = 0;
-	while (have < flen) {
-		len = read(file, &buf[have], flen - have);
-		if (len < 0) {
-			INFO("fatal: read of %s at %lld len %lld: %s",
-				 filename, (long long)have, (long long)(flen - have), strerror(errno));
-			goto outfree;
-		}
-		if (len == 0) {
-			INFO("fatal: read of %s at %lld len %lld: zero bytes read",
-				 filename, (long long)have, (long long)(flen - have));
-		outfree:
-			free(buf);
-		outclose:
-			close(file);
-			return false;
-		}
-		have += len;
-	}
-	close(file);
-	buf[flen] = 0; // NUL terminate.
-	eof = buf + flen;
-	
-	// Parse through the file line by line.
-	line = buf;
-	lineno = 1;
-	while (line < eof) { // < because NUL at eof could be last eol.
-		nextCR = strchr(line, '\r');
-		nextNL = strchr(line, '\n');
+    // Get the length of the file.
+    flen = lseek(file, 0, SEEK_END);
+    lseek(file, 0, SEEK_SET);
+    if (flen > 500 * 1024 || (buf = malloc((size_t)flen + 1)) == NULL) {
+        INFO("fatal: not enough memory for %s", filename);
+        goto outclose;
+    }
+    size_t fsize = (size_t)flen;
 
-		// Added complexity for CR/LF agnostic line endings.   Necessary?
-		if (nextNL != NULL) {
-			if (nextCR != NULL && nextCR < nextNL)
-				eol = nextCR;
-			else
-				eol = nextNL;
-		} else {
-			if (nextCR != NULL)
-				eol = nextCR;
-			else
-				eol = buf + flen;
-		}
+    // Just in case we have a read() syscall that doesn't always read the whole file at once
+    have = 0;
+    while (have < fsize) {
+        len = read(file, &buf[have], fsize - have);
+        if (len < 0) {
+            INFO("fatal: read of %s at %lld len %lld: %s",
+                 filename, (long long)have, (long long)(fsize - have), strerror(errno));
+            goto outfree;
+        }
+        if (len == 0) {
+            INFO("fatal: read of %s at %lld len %lld: zero bytes read",
+                 filename, (long long)have, (long long)(fsize - have));
+        outfree:
+            free(buf);
+        outclose:
+            close(file);
+            return false;
+        }
+        have += len;
+    }
+    close(file);
+    buf[flen] = 0; // NUL terminate.
+    eof = buf + flen;
 
-		// If this isn't a blank line or a comment line, parse it.
-		if (eol - line != 1 && line[0] != '#') {
-			*eol = 0;
-			// If we get a bad config line, we're going to return failure later, but continue parsing now.
-			if (!config_parse_line(context, filename, line, lineno, verbs, num_verbs))
-				success = false;
+    // Parse through the file line by line.
+    line = buf;
+    lineno = 1;
+    while (line < eof) { // < because NUL at eof could be last eol.
+        nextCR = strchr(line, '\r');
+        nextNL = strchr(line, '\n');
+
+        // Added complexity for CR/LF agnostic line endings.   Necessary?
+        if (nextNL != NULL) {
+            if (nextCR != NULL && nextCR < nextNL)
+                eol = nextCR;
+            else
+                eol = nextNL;
+        } else {
+            if (nextCR != NULL)
+                eol = nextCR;
+            else
+                eol = buf + flen;
+        }
+
+        // If this isn't a blank line or a comment line, parse it.
+        if (eol - line != 1 && line[0] != '#') {
+            *eol = 0;
+            // If we get a bad config line, we're going to return failure later, but continue parsing now.
+            if (!config_parse_line(context, filename, line, lineno, verbs, num_verbs))
+                success = false;
         }
         line = eol + 1;
         lineno++;
-	}		
-	free(buf);
-	return success;
+    }
+    free(buf);
+    return success;
 }
-
diff --git a/ServiceRegistration/cti-common.h b/ServiceRegistration/cti-common.h
index 1d007ba..fb2fa78 100644
--- a/ServiceRegistration/cti-common.h
+++ b/ServiceRegistration/cti-common.h
@@ -124,9 +124,11 @@
     kCTI_NetworkNodeType_Router,
     kCTI_NetworkNodeType_EndDevice,
     kCTI_NetworkNodeType_SleepyEndDevice,
+    kCTI_NetworkNodeType_SynchronizedSleepyEndDevice,
     kCTI_NetworkNodeType_NestLurker,
     kCTI_NetworkNodeType_Commissioner,
     kCTI_NetworkNodeType_Leader,
+    kCTI_NetworkNodeType_SleepyRouter,
 } cti_network_node_type_t;
 
 #define kCTIRoleDisabled 0
diff --git a/ServiceRegistration/cti-openthread.c b/ServiceRegistration/cti-openthread.c
deleted file mode 100644
index 8436043..0000000
--- a/ServiceRegistration/cti-openthread.c
+++ /dev/null
@@ -1,484 +0,0 @@
-/* cti-openthread.c
- *
- * Copyright (c) 2020 Apple Computer, Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * This code adds border router support to 3rd party HomeKit Routers as part of Apple’s commitment to the CHIP project.
- *
- * Concise Thread Interface implementation
- */
-
-#define _GNU_SOURCE 1
-#include <stdio.h>
-#include <arpa/inet.h>
-#include <string.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <syslog.h>
-#include <pwd.h>
-#include <grp.h>
-#include <sys/un.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-
-#include "cti-proto.h"
-#include "cti-server.h"
-#include "cti-openthread.h"
-
-#include <syslog.h>
-#include <openthread/config.h>
-#include <openthread/server.h>
-#include <openthread/border_router.h>
-#include <openthread/instance.h>
-#include <openthread/thread.h>
-#include <openthread/platform/misc.h>
-#include <openthread/openthread-system.h>
-
-static otInstance* cti_instance = NULL;
-void handleThreadStateChanged(uint32_t flags, void UNUSED *context);
-
-#define SuccessOrFailure(eval)                      \
-    do                                              \
-    {                                               \
-        if (!(eval))                                \
-        {                                           \
-            return kCTIStatus_UnknownError;         \
-        }                                           \
-    } while (false)
-
-// OT accessorys and mutators
-//    Implementions of the getters and setters called by cti-server
-int ctiAddService( uint32_t enterprise_number,
-                   const uint8_t* service_data,
-                   size_t service_data_length,
-                   const uint8_t* server_data,
-                   size_t server_data_length) {
-
-    otError error = OT_ERROR_NONE;
-    otServiceConfig serviceCfg;
-
-    if ( !cti_instance) {
-        return kCTIStatus_UnknownError;
-    }
-
-    if (service_data_length > sizeof(serviceCfg.mServiceData) ||
-        server_data_length > sizeof(serviceCfg.mServerConfig.mServerData)) {
-        return kCTIStatus_BadParam;
-    }
-
-    serviceCfg.mEnterpriseNumber = enterprise_number;
-    memcpy(serviceCfg.mServiceData, service_data, service_data_length);
-    serviceCfg.mServiceDataLength = service_data_length;
-    memcpy(serviceCfg.mServerConfig.mServerData, server_data, server_data_length);
-    serviceCfg.mServerConfig.mServerDataLength = server_data_length;
-    serviceCfg.mServerConfig.mStable = true;
-
-    error = otServerAddService(cti_instance, &serviceCfg);
-
-    if (error != OT_ERROR_NONE) {
-        syslog(LOG_INFO, "Failed to add service: %d", error);
-        return kCTIStatus_UnknownError;
-    }
-    error = otBorderRouterRegister(cti_instance);
-    if (error != OT_ERROR_NONE) {
-        syslog(LOG_INFO, "Failed to push service: %d", error);
-        return kCTIStatus_UnknownError;
-    }
-
-    return kCTIStatus_NoError;
-}
-
-int ctiRemoveService( uint32_t enterprise_number,
-                      const uint8_t *service_data,
-                      size_t service_data_length) {
-
-    if ( !cti_instance) {
-        return kCTIStatus_UnknownError;
-    }
-
-    otError error = otServerRemoveService(cti_instance,
-                                          enterprise_number,
-                                          service_data,
-                                          service_data_length);
-    if (error != OT_ERROR_NONE) {
-        syslog(LOG_INFO, "Failed to remove service %d", enterprise_number);
-        return kCTIStatus_UnknownError;
-    }
-    error = otBorderRouterRegister(cti_instance);
-    if (error != OT_ERROR_NONE) {
-        syslog(LOG_INFO, "Failed to push service: %d", error);
-        return kCTIStatus_UnknownError;
-    }
-    return kCTIStatus_NoError;
-}
-
-int ctiRetrieveServiceList(cti_connection_t connection, int UNUSED event)
-{
-    otNetworkDataIterator iterator = OT_NETWORK_DATA_ITERATOR_INIT;
-    otServiceConfig config;
-    if ( !cti_instance) {
-        return kCTIStatus_UnknownError;
-    }
-
-    uint16_t numServices = 0;
-    uint16_t totalSize = 0;
-    // Walk through the list and calculate the number and size of services
-    while (otNetDataGetNextService(cti_instance, &iterator, &config) == OT_ERROR_NONE) {
-        numServices++;
-
-        // We will send the data back in this order:
-        totalSize += sizeof(config.mEnterpriseNumber);   // Enterprise Num
-        totalSize += sizeof(config.mServiceDataLength);  // Size of the service data
-        totalSize += config.mServiceDataLength;         // The actual service data bytes
-        totalSize += sizeof(config.mServerConfig.mServerDataLength);  // Size of the server data
-        totalSize += config.mServerConfig.mServerDataLength;         // The actual server data bytes
-    }
-
-    totalSize += sizeof(numServices);
-
-    // Message:
-    // Num Services in List
-    //    For each Service:
-    //      Enterprise number
-    //      Service Data Length
-    //      Service Data
-    //      Server Data Length
-    //      Server Data
-    SuccessOrFailure( cti_connection_message_create(connection, kCTIMessageType_ServiceEvent, totalSize) );
-
-    // Indicate the number of services in the vector
-    SuccessOrFailure(cti_connection_u8_put(connection, numServices));
-
-    iterator = OT_NETWORK_DATA_ITERATOR_INIT;
-
-    int i = 0;
-    while ((otNetDataGetNextService(cti_instance, &iterator, &config) == OT_ERROR_NONE) && i < numServices) {
-        SuccessOrFailure(cti_connection_u32_put(connection, config.mEnterpriseNumber));
-        SuccessOrFailure(cti_connection_data_put(connection, config.mServiceData, config.mServiceDataLength));
-        SuccessOrFailure(cti_connection_data_put(connection,
-                                                 config.mServerConfig.mServerData,
-                                                 config.mServerConfig.mServerDataLength));
-    }
-
-    SuccessOrFailure( cti_connection_message_send(connection) );
-
-    return kCTIStatus_NoError;
-}
-
-int ctiAddMeshPrefix(struct in6_addr *prefix, size_t prefix_length, bool on_mesh, bool preferred, bool slaac, bool stable)
-{
-    otError error = OT_ERROR_NONE;
-    if ( !cti_instance) {
-        return kCTIStatus_UnknownError;
-    }
-    otBorderRouterConfig borderRouterConfig;
-    memset(&borderRouterConfig, 0, sizeof(borderRouterConfig));
-
-    if ( prefix_length > 64) {
-        return kCTIStatus_BadParam;
-    }
-    memcpy(borderRouterConfig.mPrefix.mPrefix.mFields.m8, prefix, sizeof(*prefix));
-
-    borderRouterConfig.mPrefix.mLength = prefix_length;
-    borderRouterConfig.mStable = stable;
-    borderRouterConfig.mPreference = OT_ROUTE_PREFERENCE_MED; // Can also be high or low.
-    borderRouterConfig.mPreferred = preferred;
-    borderRouterConfig.mSlaac = slaac;
-    borderRouterConfig.mOnMesh = on_mesh;
-    borderRouterConfig.mStable = stable;
-    borderRouterConfig.mConfigure = false;
-    borderRouterConfig.mDefaultRoute = true;
-    borderRouterConfig.mDhcp = false;
-
-    error = otBorderRouterAddOnMeshPrefix(cti_instance, &borderRouterConfig);
-    if (error != OT_ERROR_NONE) {
-        return kCTIStatus_UnknownError;
-    }
-    error = otBorderRouterRegister(cti_instance);
-    if (error != OT_ERROR_NONE) {
-        syslog(LOG_INFO, "Failed to push service: %d", error);
-        return kCTIStatus_UnknownError;
-    }
-
-    return kCTIStatus_NoError;
-}
-
-int ctiRemoveMeshPrefix(struct in6_addr *prefix, size_t prefix_length) {
-
-    if (!cti_instance) {
-        return kCTIStatus_UnknownError;
-    }
-
-    otIp6Prefix ip6Prefix;
-    memset(&ip6Prefix, 0, sizeof(ip6Prefix));
-
-    if (prefix_length > 64) {
-        return kCTIStatus_BadParam;
-    }
-    memcpy(ip6Prefix.mPrefix.mFields.m8, prefix, sizeof(*prefix));
-    ip6Prefix.mLength = prefix_length;
-    otError error = otBorderRouterRemoveOnMeshPrefix(cti_instance, &ip6Prefix);
-    if (error != OT_ERROR_NONE && error != OT_ERROR_NOT_FOUND) {
-        return kCTIStatus_UnknownError;
-    }
-    error = otBorderRouterRegister(cti_instance);
-    if (error != OT_ERROR_NONE) {
-        syslog(LOG_INFO, "Failed to push service: %d", error);
-        return kCTIStatus_UnknownError;
-    }
-    return kCTIStatus_NoError;
-}
-
-int ctiRetrievePrefixList(cti_connection_t connection, int UNUSED event)
-{
-    if (!cti_instance) {
-        return kCTIStatus_UnknownError;
-    }
-    otBorderRouterConfig config;
-    otNetworkDataIterator iterator = OT_NETWORK_DATA_ITERATOR_INIT;
-
-    uint16_t numPrefixes = 0;
-    int totalSize = 0;
-    do {
-        int error = otNetDataGetNextOnMeshPrefix(cti_instance, &iterator, &config);
-        if (error != OT_ERROR_NONE) {
-            if (error != OT_ERROR_NOT_FOUND) {
-                syslog(LOG_ERR, "ctiRetrievePrefixList: otBorderRouterGetNextRoute: %d", error);
-                return kCTIStatus_UnknownError;
-            }
-            break;
-        }
-        numPrefixes++;
-        totalSize += 1;  // 1 byte stability / source flag.
-        totalSize += sizeof (config.mPrefix.mLength);
-        totalSize +=  config.mPrefix.mLength; // size of the prefix
-    } while (1);
-
-    // Message:
-    // Num prefixes in List
-    //    For each prefix:
-    //      flags
-    //      prefix length
-    //      prefix
-    SuccessOrFailure( cti_connection_message_create(connection, kCTIMessageType_PrefixEvent, totalSize) );
-
-    // Indicate the number of services in the vector
-    SuccessOrFailure(cti_connection_u16_put(connection, numPrefixes));
-
-    iterator = OT_NETWORK_DATA_ITERATOR_INIT;
-
-    int i = 0;
-    while (i < numPrefixes)
-    {
-        int error = otNetDataGetNextOnMeshPrefix(cti_instance, &iterator, &config);
-        if (error != OT_ERROR_NONE) {
-            if (error != OT_ERROR_NOT_FOUND) {
-                syslog(LOG_ERR, "ctiRetrievePrefixList: otBorderRouterGetNextRoute: %d", error);
-            }
-            break;
-        }
-        // DJC Note:  Shouldn't be possible for the items in the Thread Network data to have changed
-        // between above count and now, but I'm paranoid and assumptions rot over time.
-        uint16_t flags = 0;
-        flags |= config.mDefaultRoute ? 0 : kCTIFlag_NCP;
-        flags |= config.mStable ? kCTIFlag_Stable : 0;
-
-        SuccessOrFailure(cti_connection_u16_put(connection, flags));
-        SuccessOrFailure(cti_connection_u8_put(connection, config.mPrefix.mLength));
-        SuccessOrFailure(cti_connection_data_put(connection, config.mPrefix.mPrefix.mFields.m8, 8));
-    }
-
-    SuccessOrFailure( cti_connection_message_send(connection) );
-    return kCTIStatus_NoError;
-}
-
-int ctiRetrievePartitionId(cti_connection_t connection, int UNUSED event)
-{
-    uint32_t partitionId = otThreadGetPartitionId(cti_instance);
-    SuccessOrFailure(cti_connection_message_create(connection, kCTIMessageType_UInt64PropEvent,
-                                                   sizeof(partitionId) + sizeof(uint32_t)));
-    SuccessOrFailure(cti_connection_u32_put(connection, kCTIPropertyPartitionID));
-    SuccessOrFailure(cti_connection_u64_put(connection, partitionId));
-    SuccessOrFailure(cti_connection_message_send(connection));
-    return kCTIStatus_NoError;
-}
-
-int ctiRetrieveXPANID(cti_connection_t connection, int UNUSED event)
-{
-    const otExtendedPanId *panid_buf = otThreadGetExtendedPanId(cti_instance);
-    uint64_t xpanid = 0;
-    for (int i = 7; i >= 0; i--) {
-        xpanid = (xpanid << 8) | panid_buf->m8[i];
-    }
-    SuccessOrFailure(cti_connection_message_create(connection, kCTIMessageType_UInt64PropEvent,
-                                                   sizeof(xpanid) + sizeof(uint32_t)));
-    SuccessOrFailure(cti_connection_u32_put(connection, kCTIPropertyExtendedPANID));
-    SuccessOrFailure(cti_connection_u64_put(connection, xpanid));
-    SuccessOrFailure(cti_connection_message_send(connection));
-    return kCTIStatus_NoError;
-}
-
-int ctiRetrieveTunnel(cti_connection_t connection) {
-    const char *interfaceName = "wpan0";
-    uint32_t interfaceIndex = 0;
-#if defined(OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE) && OPENTHREAD_CONFIG_PLATFORM_NETIF_ENABLE
-    otError error = otPlatGetNetif(cti_instance, &interfaceName, &interfaceIndex);
-    if (error != OT_ERROR_NONE) {
-        return kCTIStatus_UnknownError;
-    }
-#endif
-    (void)interfaceIndex;
-    SuccessOrFailure(cti_connection_message_create(connection,
-                                                   kCTIMessageType_TunnelNameResponse,
-                                                   2 + strlen(interfaceName)));
-    SuccessOrFailure(cti_connection_string_put(connection, interfaceName));
-    SuccessOrFailure(cti_connection_message_send(connection));
-    return kCTIStatus_NoError;
-}
-
-/*  This will return one of the following:
- *      OT_DEVICE_ROLE_DISABLED = 0, ///< The Thread stack is disabled.
- *      OT_DEVICE_ROLE_DETACHED = 1, ///< Not currently participating in a Thread network/partition.
- *      OT_DEVICE_ROLE_CHILD    = 2, ///< The Thread Child role.
- *      OT_DEVICE_ROLE_ROUTER   = 3, ///< The Thread Router role.
- *      OT_DEVICE_ROLE_LEADER   = 4, ///< The Thread Leader role.
- * Note:  Disabled = "OFF", Detached = "Associating", Others = "Associated"
- */
-int
-ctiRetrieveNodeType(cti_connection_t connection, int event)
-{
-    otDeviceRole role = otThreadGetDeviceRole(cti_instance);
-    uint8_t datum;
-    int event_type;
-
-    if (event == CTI_EVENT_ROLE) {
-        event_type = kCTIMessageType_RoleEvent;
-        switch(role) {
-        default:
-        case OT_DEVICE_ROLE_DISABLED:
-        case OT_DEVICE_ROLE_DETACHED:
-            datum = kCTI_NetworkNodeType_Unknown;
-            break;
-        case OT_DEVICE_ROLE_CHILD:
-            datum = kCTI_NetworkNodeType_EndDevice;
-            break;
-        case OT_DEVICE_ROLE_ROUTER:
-            datum = kCTI_NetworkNodeType_Router;
-            break;
-        case OT_DEVICE_ROLE_LEADER:
-            datum = kCTI_NetworkNodeType_Leader;
-            break;
-        }
-    } else {
-        event_type = kCTIMessageType_StateEvent;
-        switch(role) {
-        default:
-        case OT_DEVICE_ROLE_DISABLED:
-            datum = kCTI_NCPState_Offline;
-            break;
-        case OT_DEVICE_ROLE_DETACHED:
-            datum = kCTI_NCPState_Associating;
-            break;
-        case OT_DEVICE_ROLE_CHILD:
-        case OT_DEVICE_ROLE_ROUTER:
-        case OT_DEVICE_ROLE_LEADER:
-            datum = kCTI_NCPState_Associated;
-            break;
-        }
-    }
-    if (cti_connection_message_create(connection, event_type, 1) &&
-        cti_connection_u8_put(connection, datum) &&
-        cti_connection_message_send(connection)) {
-    }
-    return 0;
-}
-
-void
-handleThreadStateChanged(uint32_t flags, void UNUSED *context)
-{
-    syslog(LOG_INFO, "handleThreadStateChanged: flags = %" PRIx32, flags);
-
-    if ( !cti_instance) {
-        return;
-    }
-
-    syslog(LOG_INFO, "Thread state changed, flag: %d", flags );
-    if ( flags & OT_CHANGED_THREAD_ROLE)
-    {
-        syslog(LOG_INFO, "    Thread Role changed.  Notify registered clients" );
-        cti_notify_event(CTI_EVENT_ROLE, ctiRetrieveNodeType);
-        cti_notify_event(CTI_EVENT_STATE, ctiRetrieveNodeType);
-    }
-    if ( flags & OT_CHANGED_THREAD_NETDATA)
-    {
-        syslog(LOG_INFO, "    Thread Netdata changed.  Notify registered clients" );
-        cti_notify_event(CTI_EVENT_SERVICE, ctiRetrieveServiceList);
-        cti_notify_event(CTI_EVENT_PREFIX, ctiRetrievePrefixList);
-
-    }
-    if ( flags & OT_CHANGED_THREAD_PARTITION_ID)
-    {
-        syslog(LOG_INFO, "    Thread Partition ID changed.  Notify registered clients" );
-        cti_notify_event(CTI_EVENT_PARTITION_ID, ctiRetrievePartitionId);
-    }
-    if ( flags & OT_CHANGED_THREAD_EXT_PANID)
-    {
-        syslog(LOG_INFO, "    Thread Partition ID changed.  Notify registered clients" );
-        cti_notify_event(CTI_EVENT_XPANID, ctiRetrievePartitionId);
-    }
-
-}
-
-// Functions to be called by ot-daemon's main:
-// Called from main after InitInstance()
-void otCtiServerInit(otInstance*  aInstance) {
-    cti_instance = aInstance;
-    otError error = otSetStateChangedCallback(cti_instance, handleThreadStateChanged, NULL);
-    if(error != OT_ERROR_NONE && error != OT_ERROR_ALREADY) {
-        syslog(LOG_INFO, "otCtiServerInit: otSetStateChangedCallback: Unable to register: %d", error);
-    }
-
-    cti_init();
-}
-
-// Called in main's idle loop after FD Prep.
-void otCtiServerUpdate(otInstance* aInstance, otSysMainloopContext *aMainloop) {
-    int nfds = 0;
-    cti_instance = aInstance;
-    // Note:  This will not compile as it requires a change to cti_fd_init's arg list since
-    // ot-daemon's make does not allow unused arguments.
-    cti_fd_init(&nfds, &aMainloop->mReadFdSet);
-    if (aMainloop->mMaxFd < nfds) {
-        aMainloop->mMaxFd = nfds;
-    }
-}
-
-// Called in main loop after a successful otSysMainloopPoll
-void otCtiServerProcess(otInstance* aInstance, otSysMainloopContext *aMainloop) {
-    cti_instance = aInstance;
-    // Note: this will not compile as it is as it removes unused args in cti_fd_process
-    cti_fd_process(&aMainloop->mReadFdSet);
-}
-
-// Local Variables:
-// mode: C
-// tab-width: 4
-// c-file-style: "bsd"
-// c-basic-offset: 4
-// fill-column: 108
-// indent-tabs-mode: nil
-// End:
diff --git a/ServiceRegistration/cti-openthread.h b/ServiceRegistration/cti-openthread.h
deleted file mode 100644
index 053dcbf..0000000
--- a/ServiceRegistration/cti-openthread.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/* cti-openthread.h
- *
- * Copyright (c) 2020 Apple Computer, Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * External function signatures for CTI entry points used by OpenThread main loop.
- */
-
-#ifndef __CTI_SERVER_H__
-#define __CTI_SERVER_H__ 1
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include "cti-server.h"
-#include "cti-common.h"
-#include <openthread/instance.h>
-#include <openthread/openthread-system.h>
-
-
-void otCtiServerInit(otInstance *NONNULL aInstance);
-void otCtiServerUpdate(otInstance *NONNULL aInstance, otSysMainloopContext *NONNULL aMainloop);
-void otCtiServerProcess(otInstance* aInstance, otSysMainloopContext *aMainloop);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif // __CTI_SERVER_H__
diff --git a/ServiceRegistration/cti-proto.c b/ServiceRegistration/cti-proto.c
deleted file mode 100644
index 5051d40..0000000
--- a/ServiceRegistration/cti-proto.c
+++ /dev/null
@@ -1,614 +0,0 @@
-/* cti-proto.c
- *
- * Copyright (c) 2020 Apple Computer, Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * CTI protocol communication primitives
- */
-
-#define _GNU_SOURCE 1
-#include <stdio.h>
-#include <arpa/inet.h>
-#include <string.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <syslog.h>
-#include <stdint.h>
-#include <pwd.h>
-#include <grp.h>
-#include <sys/un.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/signal.h>
-
-#ifdef OPENTHREAD_PLATFORM_POSIX
-#include "cti-server.h"
-#include "cti-proto.h"
-#else
-#include "cti-proto.h"
-#include "cti-common.h"
-#endif
-
-
-void
-cti_connection_finalize(cti_connection_t connection)
-{
-	if (connection->input.buffer) {
-		free(connection->input.buffer);
-	}
-	if (connection->output.buffer) {
-		free(connection->output.buffer);
-	}
-	free(connection);
-}
-
-void
-dump_to_hex(uint8_t *data, size_t length, char *buffer, int len)
-{
-	char *s = buffer;
-	unsigned int i;
-	for (i = 0; i < length; i++) {
-		size_t avail = len - (s - buffer);
-		if (i == 0) {
-			snprintf(s, avail, "%02x", data[i]);
-		} else {
-			snprintf(s, avail, ":%02x", data[i]);
-		}
-		s += strlen(s);
-	}
-}
-
-bool
-cti_make_space(cti_buffer_t *buf, size_t space)
-{
-	if (buf->buffer == NULL) {
-		buf->current = 0;
-		buf->size = space;
-		buf->buffer = malloc(space);
-		if (buf->buffer == NULL) {
-			syslog(LOG_ERR, "cti_make_space: no memory for %zd bytes.", space);
-			return false;
-		}
-	}
-	if (buf->current + space > buf->size) {
-		size_t next_increment = buf->size * 2;
-		if (next_increment + space > buf->size) {
-			next_increment += space;
-		}
-		void *new_buf = malloc(next_increment);
-		if (new_buf == NULL) {
-			syslog(LOG_ERR, "cti_make_space: no memory for %zd bytes.", next_increment);
-			return false;
-		}
-		memcpy(new_buf, buf->buffer, buf->current);
-		free(buf->buffer);
-		buf->buffer = new_buf;
-        buf->size = next_increment;
-	}
-	return true;
-}
-
-static bool
-cti_put(cti_connection_t connection, const void *data, size_t length)
-{
-	if (!cti_make_space(&connection->output, length)) {
-		cti_connection_close(connection);
-		return false;
-	}
-
-	memcpy(connection->output.buffer + connection->output.current, data, length);
-	connection->output.current += length;
-	return true;
-}
-
-bool
-cti_connection_u64_put(cti_connection_t connection, uint64_t val)
-{
-	uint32_t rvals[2];
-    rvals[0] = htonl((val >> 32));
-    rvals[1] = htonl((val & 0xffffffffL));
-	return cti_put(connection, &rvals, sizeof(rvals));
-}
-
-bool
-cti_connection_u32_put(cti_connection_t connection, uint32_t val)
-{
-	uint32_t rval = htonl(val);
-	return cti_put(connection, &rval, sizeof(rval));
-}
-
-bool
-cti_connection_i32_put(cti_connection_t connection, int32_t val)
-{
-	uint32_t uval = htonl((uint32_t)val);
-	return cti_connection_u32_put(connection, uval);
-}
-
-bool
-cti_connection_u16_put(cti_connection_t connection, uint16_t val)
-{
-	uint16_t rval = htons(val);
-	return cti_put(connection, &rval, sizeof(rval));
-}
-
-bool
-cti_connection_u8_put(cti_connection_t connection, uint8_t val)
-{
-	uint8_t rval = val;
-	return cti_put(connection, &rval, sizeof(rval));
-}
-
-bool
-cti_connection_bool_put(cti_connection_t connection, bool val)
-{
-	uint8_t rval = val ? 1 : 0;
-	return cti_put(connection, &rval, sizeof(rval));
-}
-
-bool
-cti_connection_data_put(cti_connection_t connection, const void *data, uint16_t length)
-{
-	return (cti_connection_u16_put(connection, length) && cti_put(connection, data, length));
-}
-
-bool
-cti_connection_string_put(cti_connection_t connection, const char *string)
-{
-	uint16_t len;
-    if (string == NULL) {
-        return cti_connection_u16_put(connection, 0xffff);
-    }
-    len = strlen(string);
-    return (cti_connection_u16_put(connection, len) && cti_put(connection, string, len));
-}
-
-bool
-cti_parse(cti_connection_t connection, void *buffer, uint16_t length)
-{
-	if (length > connection->message_length - connection->input.current) {
-		syslog(LOG_ERR, "cti_parse: bogus data element length %d exceeds available space %zd",
-			   length, connection->message_length - connection->input.current);
-		cti_connection_close(connection);
-		return false;
-	}
-
-	memcpy(buffer, connection->input.buffer + connection->input.current, length);
-	connection->input.current += length;
-	return true;
-}
-
-bool
-cti_connection_u64_parse(cti_connection_t connection, uint64_t *val)
-{
-	uint32_t rvals[2];
-	if (!cti_parse(connection, &rvals, sizeof(rvals))) {
-		return false;
-	}
-	*val = ((uint64_t)(ntohl(rvals[0])) << 32) | ntohl(rvals[1]);
-	return true;
-}
-
-bool
-cti_connection_u32_parse(cti_connection_t connection, uint32_t *val)
-{
-	uint32_t rval;
-	if (!cti_parse(connection, &rval, sizeof(rval))) {
-		return false;
-	}
-	*val = ntohl(rval);
-	return true;
-}
-
-bool
-cti_connection_i32_parse(cti_connection_t connection, int32_t *val)
-{
-	uint32_t uval;
-	if (!cti_parse(connection, &uval, sizeof(uval))) {
-		return false;
-	}
-	*val = (int32_t)ntohl(uval);
-	return true;
-}
-
-bool
-cti_connection_u16_parse(cti_connection_t connection, uint16_t *val)
-{
-	uint16_t rval;
-	if (!cti_parse(connection, &rval, sizeof(rval))) {
-		return false;
-	}
-	*val = ntohs(rval);
-	return true;
-}
-
-bool
-cti_connection_u8_parse(cti_connection_t connection, uint8_t *val)
-{
-    uint8_t rval;
-	if (cti_parse(connection, &rval, sizeof(rval))) {
-        *val = rval;
-        return true;
-    }
-    return false;
-}
-
-bool
-cti_connection_bool_parse(cti_connection_t connection, bool *val)
-{
-	uint8_t rval;
-	if (!cti_parse(connection, &rval, sizeof(rval))) {
-		return false;
-	}
-	*val = rval == 0 ? false : true;
-	return true;
-}
-
-bool
-cti_connection_data_parse(cti_connection_t connection, void **data, uint16_t *length)
-{
-	uint16_t len;
-	void *ret;
-	if (!cti_connection_u16_parse(connection, &len)) {
-		return false;
-	}
-	if (len > connection->message_length - connection->input.current) {
-		syslog(LOG_ERR, "cti_connection_data_parse: bogus data element length %d exceeds available space %zd",
-			   len, connection->message_length - connection->input.current);
-		cti_connection_close(connection);
-		return false;
-	}
-	ret = malloc(len);
-	if (ret == NULL) {
-		syslog(LOG_ERR, "cti_connection_data_parse: out of memory for %d byte data element", len);
-		cti_connection_close(connection);
-		return false;
-	}
-	if (!cti_parse(connection, ret, len)) {
-		free(ret);
-		return false;
-	}
-    *length = len;
-	*data = ret;
-	return true;
-}
-
-bool
-cti_connection_string_parse(cti_connection_t connection, char **string)
-{
-	uint16_t len;
-	char *ret;
-	if (!cti_connection_u16_parse(connection, &len)) {
-		return false;
-	}
-    if (len == 0xffff) {
-        *string = NULL;
-        return true;
-    }
-	if (len > connection->message_length - connection->input.current) {
-		syslog(LOG_ERR, "cti_connection_string_parse: bogus data element length %d exceeds available space %zd",
-			   len, connection->message_length - connection->input.current);
-		cti_connection_close(connection);
-		return false;
-	}
-	ret = malloc(len + 1);
-	if (ret == NULL) {
-		syslog(LOG_ERR, "cti_connection_string_parse: out of memory for %d byte string", len);
-		cti_connection_close(connection);
-		return false;
-	}
-	if (!cti_parse(connection, ret, len)) {
-		free(ret);
-		return false;
-	}
-    ret[len] = 0;
-	*string = ret;
-	return true;
-}
-
-void
-cti_connection_parse_start(cti_connection_t connection)
-{
-	connection->input.current = 0;
-}
-
-bool
-cti_connection_parse_done(cti_connection_t connection)
-{
-	if (connection->input.current != connection->message_length) {
-		syslog(LOG_ERR, "cti_connection_parse_done: %zd bytes of junk at end of message",
-			   connection->message_length - connection->input.current);
-		return false;
-	}
-	return true;
-}
-
-bool
-cti_connection_message_create(cti_connection_t connection, int message_type, uint16_t space)
-{
-	connection->output.current = 0;
-	if (connection->output.buffer != NULL) {
-		if (connection->output.size < space) {
-			free(connection->output.buffer);
-			connection->output.buffer = NULL;
-		}
-	}
-    // +4 for the length and the message type, which the caller isn't expected to track.
-	if (!cti_make_space(&connection->output, space + 4)) {
-		cti_connection_close(connection);
-		return false;
-	}
-	// Space for length, which is stored last.
-	connection->output.current = 2;
-	return cti_connection_u16_put(connection, message_type);
-}
-
-bool
-cti_connection_message_send(cti_connection_t connection)
-{
-	size_t offset = connection->output.current;
-    uint16_t len;
-	ssize_t result;
-	connection->output.current = 0;
-	if (offset > UINT16_MAX) {
-		syslog(LOG_ERR, "cti_connection_send: too big (%zd)", offset);
-	out:
-		cti_connection_close(connection);
-		return false;
-	}
-    len = (uint16_t)offset - 2;
-	if (!cti_connection_u16_put(connection, len)) {
-		return false;
-	}
-	result = write(connection->fd, connection->output.buffer, offset);
-	if (result < 0) {
-		syslog(LOG_ERR, "cti_connection_send: write: %s", strerror(errno));
-		goto out;
-	}
-	if ((size_t)result != offset) {
-		syslog(LOG_ERR, "cti_connection_send: short write: %zd instead of %zd", result, offset);
-		goto out;
-	}
-	return true;
-}
-
-bool
-cti_send_response(cti_connection_t connection, int status)
-{
-	if (cti_connection_message_create(connection, kCTIMessageType_Response, 10) &&
-		cti_connection_u16_put(connection, connection->message_type) &&
-		cti_connection_u32_put(connection, status))
-	{
-		return cti_connection_message_send(connection);
-	}
-	return false;
-}
-
-void
-cti_read(cti_connection_t connection, cti_datagram_callback_t datagram_callback)
-{
-	size_t needed = connection->input.expected - connection->input.current;
-	if (needed > 0) {
-		if (!cti_make_space(&connection->input, needed)) {
-			cti_connection_close(connection);
-			return;
-		}
-		ssize_t result = read(connection->fd, connection->input.buffer + connection->input.current, needed);
-		if (result < 0) {
-			syslog(LOG_INFO, "cti_read_callback: read: %s", strerror(errno));
-			cti_connection_close(connection);
-			return;
-		}
-        if (result == 0) {
-            syslog(LOG_INFO, "cti_read_callback: remote close");
-            cti_connection_close(connection);
-            return;
-        }
-		connection->input.current += result;
-		if ((size_t)result < needed) {
-			return;
-		}
-	}
-	// We have finished reading the length of the next message.
-	if (connection->message_length == 0) {
-		if (connection->input.expected != 2) {
-			syslog(LOG_ERR, "cti_read_callback: invalid expected length: %zd", connection->input.expected);
-			cti_connection_close(connection);
-			return;
-		}
-		connection->message_length = ((size_t)connection->input.buffer[0] << 8) + connection->input.buffer[1];
-		connection->input.current = 0;
-		connection->input.expected = connection->message_length;
-		return;
-	}
-	// We have finished reading a message.
-	datagram_callback(connection);
-
-	// Read the next one.
-	connection->input.expected = 2;
-	connection->message_length = 0;
-	connection->input.current = 0;
-}
-
-cti_connection_t
-cti_connection_allocate(uint16_t expected_size)
-{
-	cti_connection_t connection = calloc(1, sizeof(*connection));
-	if (connection == NULL) {
-		syslog(LOG_ERR, "cti_accept: no memory for connection structure.");
-		return NULL;
-	}
-	if (!cti_make_space(&connection->input, expected_size)) {
-		cti_connection_finalize(connection);
-		return NULL;
-	}
-	connection->input.expected = 2;
-	return connection;
-}
-
-int
-cti_make_unix_socket(const char *sockname, size_t name_size, bool is_listener)
-{
-    struct sockaddr_un addr;
-    int fd;
-
-    if (is_listener && (unlink(sockname) < 0 && errno != ENOENT)) {
-        syslog(LOG_ERR, "cti_make_unix_socket: unlink(%s: %s", sockname, strerror(errno));
-        return -1;
-    }
-
-    addr.sun_family = AF_LOCAL;
-    if (name_size > sizeof(addr.sun_path)) {
-        syslog(LOG_ERR, "cti_make_unix_socket: no space for unix socket named %s.", sockname);
-        return -1;
-    }
-    strncpy(addr.sun_path, sockname, sizeof(addr.sun_path));
-#ifndef NOT_HAVE_SA_LEN
-    addr.sun_len = strlen(addr.sun_path) + 1 + sizeof(addr.sun_len) + sizeof(addr.sun_family);
-#endif
-
-    fd = socket(AF_LOCAL, SOCK_STREAM, 0);
-    if (fd < 0) {
-        syslog(LOG_ERR, "cti_make_unix_socket: socket: %s", strerror(errno));
-        return -1;
-    }
-
-    if (is_listener) {
-        if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
-            syslog(LOG_ERR, "cti_make_unix_socket: %s", strerror(errno));
-            goto out;
-        }
-
-        if (listen(fd, 1) < 0) {
-            syslog(LOG_ERR, "cti_make_unix_socket: listen: %s", strerror(errno));
-            goto out;
-        }
-    } else {
-        if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
-            syslog(LOG_ERR, "cti_make_unix_socket: connect: %s", strerror(errno));
-            goto out;
-        }
-
-#ifdef SO_NOSIGPIPE
-        int one = 1;
-        if (setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof one) < 0) {
-            syslog(LOG_ERR, "cti_make_unix_socket: SO_NOSIGPIPE failed: %s", strerror(errno));
-            goto out;
-        }
-#endif
-        if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) {
-            syslog(LOG_ERR, "cti_make_unix_socket: can't set O_NONBLOCK: %s", strerror(errno));
-        out:
-            close(fd);
-            return -1;
-        }
-    }
-    return fd;
-}
-
-int
-cti_accept(int listen_fd, uid_t *p_uid, gid_t *p_gid, pid_t *p_pid)
-{
-    struct sockaddr_un addr;
-    socklen_t socksize = sizeof(addr);
-    int fd = accept(listen_fd, (struct sockaddr *)&addr, &socksize);
-
-    if (fd < 0) {
-        syslog(LOG_ERR, "cti_accept: accept: %s", strerror(errno));
-        return -1;
-    }
-
-#if defined(LINUX) || OPENTHREAD_PLATFORM_POSIX
-    struct ucred ucred;
-    socklen_t len = sizeof(struct ucred);
-    if (getsockopt(fd, SOL_SOCKET, SO_PEERCRED, &ucred, &len) < 0) {
-        syslog(LOG_ERR, "cti_accept: unable to get peer credentials for incoming connection: %s", strerror(errno));
-        goto out;
-    }
-
-    if (ucred.uid != 0) {
-        char **s;
-        struct group *group = getgrnam("cti-clients");
-        if (group == NULL) {
-            syslog(LOG_ERR, "cti_accept: connecting user %d is not root and there is no cti-clients group.", ucred.uid);
-            goto out;
-        } else if (group->gr_gid == ucred.gid) {
-        } else {
-            struct passwd *passwd = getpwuid(ucred.uid);
-            if (passwd == NULL || passwd->pw_name == NULL) {
-                syslog(LOG_ERR, "cti_accept: connecting user %d is not root and has no username.", ucred.uid);
-                goto out;
-            } else if (group->gr_mem == NULL || *group->gr_mem == NULL) {
-                syslog(LOG_ERR, "cti_accept: connecting user %s is not a member of cti-clients group.", passwd->pw_name);
-                goto out;
-            } else {
-                for (s = group->gr_mem; s != NULL && *s != NULL; s++) {
-                    if (!strcmp(*s, passwd->pw_name)) {
-                        break;
-                    }
-                }
-                if (*s == NULL) {
-                    syslog(LOG_ERR, "cti_accept: connecting user %s is not a member of cti-clients group.", passwd->pw_name);
-                    goto out;
-                }
-            }
-        }
-    }
-#else
-    syslog(LOG_ERR, "cti_accept: no way to validate user credentials.");
-#endif
-
-#ifdef SO_NOSIGPIPE
-    int one = 1;
-    if (setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &one, sizeof one) < 0) {
-        syslog(LOG_ERR, "SO_NOSIGPIPE failed: %s", strerror(errno));
-    }
-#endif
-    if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) {
-        syslog(LOG_ERR, "cti_accept: can't set O_NONBLOCK: %s", strerror(errno));
-        goto out;
-    }
-
-#if defined(LINUX) || OPENTHREAD_PLATFORM_POSIX
-    if (p_uid != NULL) {
-        *p_uid = ucred.uid;
-    }
-    if (p_gid != NULL) {
-        *p_gid = ucred.gid;
-    }
-    if (p_pid != NULL) {
-        *p_pid = ucred.pid;
-    }
-    syslog(LOG_INFO, "cti_accept: connection from user %d accepted", ucred.uid);
-#else
-    syslog(LOG_INFO, "cti_accept: connection from unknown user accepted");
-#endif
-    goto done;
-
-out:
-    close(fd);
-    fd = -1;
-done:
-    return fd;
-}
-
-// Local Variables:
-// mode: C
-// tab-width: 4
-// c-file-style: "bsd"
-// c-basic-offset: 4
-// fill-column: 108
-// indent-tabs-mode: nil
-// End:
diff --git a/ServiceRegistration/cti-proto.h b/ServiceRegistration/cti-proto.h
deleted file mode 100644
index e9038bf..0000000
--- a/ServiceRegistration/cti-proto.h
+++ /dev/null
@@ -1,100 +0,0 @@
-/* cti-proto.h
- *
- * Copyright (c) 2020 Apple Computer, Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * CTI protocol definitions
- */
-
-#ifndef __CTI_PROTO_H__
-#define __CTI_PROTO_H__
-#define CTI_SERVER_SOCKET_NAME "/var/run/cti-server-socket"
-
-#include <stdbool.h>
-#include "cti-common.h"
-
-typedef struct cti_buffer cti_buffer_t;
-struct cti_buffer {
-	size_t expected;
-	size_t current;
-	size_t size;
-	uint8_t *NULLABLE buffer;
-};
-
-#ifndef NO_IOLOOP
-#ifndef __CTI_SERVICES_H__
-typedef union {
-	void (*NONNULL callback)(void);
-	void (*NONNULL reply)(cti_connection_t NONNULL connection, void *NULLABLE result, int status);
-} cti_callback_t;
-#endif
-typedef void (*cti_internal_callback_t)(cti_connection_t NONNULL conn_ref, void *NULLABLE object, cti_status_t status);
-#endif
-
-struct _cti_connection_t {
-#ifdef NO_IOLOOP
-	cti_connection_t NULLABLE next;
-    uint16_t registered_event_flags;
-#else
-	void *NULLABLE io_context;
-	void *NULLABLE context;
-	int ref_count;
-	cti_callback_t callback;
-	cti_internal_callback_t NULLABLE internal_callback;
-	uid_t uid;
-	gid_t gid;
-	pid_t pid;
-#endif
-	int fd;
-	cti_buffer_t input, output;
-	size_t message_length;
-	uint16_t message_type;
-	bool message_valid;
-};
-
-typedef void (*cti_datagram_callback_t)(cti_connection_t NONNULL connection);
-void cti_connection_finalize(cti_connection_t NONNULL connection);
-void cti_connection_close(cti_connection_t NONNULL connection);
-#define cti_connection_release(connection) cti_connection_release_(connection, __FILE__, __LINE__)
-void cti_connection_release_(cti_connection_t NONNULL connection, const char *NONNULL file, int line);
-void dump_to_hex(uint8_t *NONNULL data, size_t length, char *NONNULL buffer, int len);
-bool cti_make_space(cti_buffer_t *NONNULL buf, size_t space);
-bool cti_connection_begin(cti_connection_t NONNULL connection, size_t space);
-bool cti_connection_u64_put(cti_connection_t NONNULL connection, uint64_t val);
-bool cti_connection_i32_put(cti_connection_t NONNULL connection, int32_t val);
-bool cti_connection_u32_put(cti_connection_t NONNULL connection, uint32_t val);
-bool cti_connection_u16_put(cti_connection_t NONNULL connection, uint16_t val);
-bool cti_connection_u8_put(cti_connection_t NONNULL connection, uint8_t val);
-bool cti_connection_bool_put(cti_connection_t NONNULL connection, bool val);
-bool cti_connection_u64_parse(cti_connection_t NONNULL connection, uint64_t *NONNULL val);
-bool cti_connection_i32_parse(cti_connection_t NONNULL connection, int32_t *NONNULL val);
-bool cti_connection_u32_parse(cti_connection_t NONNULL connection, uint32_t *NONNULL val);
-bool cti_connection_u16_parse(cti_connection_t NONNULL connection, uint16_t *NONNULL val);
-bool cti_connection_u8_parse(cti_connection_t NONNULL connection, uint8_t *NONNULL val);
-bool cti_connection_bool_parse(cti_connection_t NONNULL connection, bool *NONNULL val);
-bool cti_connection_data_put(cti_connection_t NONNULL connection, const void *NONNULL data, uint16_t length);
-bool cti_connection_string_put(cti_connection_t NONNULL connection, const char *NONNULL data);
-bool cti_connection_data_parse(cti_connection_t NONNULL connection,
-							   void *NONNULL *NULLABLE data, uint16_t *NONNULL length);
-bool cti_connection_string_parse(cti_connection_t NONNULL connection, char *NONNULL *NULLABLE string);
-void cti_connection_parse_start(cti_connection_t NONNULL connection);
-bool cti_connection_parse_done(cti_connection_t NONNULL connection);
-bool cti_connection_message_create(cti_connection_t NONNULL connection, int message_type, uint16_t space);
-bool cti_connection_message_send(cti_connection_t NONNULL connection);
-bool cti_send_response(cti_connection_t NONNULL connection, int status);
-void cti_read(cti_connection_t NONNULL connection, cti_datagram_callback_t NONNULL datagram_callback);
-cti_connection_t NULLABLE cti_connection_allocate(uint16_t expected_size);
-int cti_make_unix_socket(const char *NONNULL sockname, size_t name_size, bool is_listener);
-int cti_accept(int listen_fd, uid_t *NULLABLE p_uid, gid_t *NULLABLE p_gid, pid_t *NULLABLE p_pid);
-#endif // __CTI_PROTO_H__
diff --git a/ServiceRegistration/cti-server.c b/ServiceRegistration/cti-server.c
deleted file mode 100644
index e4b11d6..0000000
--- a/ServiceRegistration/cti-server.c
+++ /dev/null
@@ -1,407 +0,0 @@
-/* cti-server.c
- *
- * Copyright (c) 2020 Apple Computer, Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * This code adds border router support to 3rd party HomeKit Routers as part of Apple’s commitment to the CHIP project.
- *
- * Concise Thread Interface Server
- */
-
-#define _GNU_SOURCE 1
-#include <stdio.h>
-#include <arpa/inet.h>
-#include <string.h>
-#include <stdlib.h>
-#include <unistd.h>
-#include <errno.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include <syslog.h>
-#include <pwd.h>
-#include <grp.h>
-#include <sys/un.h>
-#include <sys/types.h>
-#include <sys/socket.h>
-#include <sys/signal.h>
-#include "cti-server.h"
-#include "cti-proto.h"
-
-#include <syslog.h>
-
-int cti_listener_fd;
-cti_connection_t connections;
-
-void
-cti_connection_close(cti_connection_t connection)
-{
-	if (connection->fd != -1) {
-		close(connection->fd);
-		connection->fd = -1;
-	}
-}
-
-static void
-cti_service_add_parse(cti_connection_t connection)
-{
-    uint32_t enterprise_id;
-    uint16_t service_data_length;
-    uint16_t server_data_length;
-    int status = kCTIStatus_NoError;
-    void *service_data;
-    void *server_data;
-    char service_data_buf[13], server_data_buf[55];
-
-    // And statement will fail as soon as anything fails to parse.
-    if (cti_connection_u32_parse(connection, &enterprise_id) &&
-        cti_connection_data_parse(connection, &service_data, &service_data_length) &&
-        cti_connection_data_parse(connection, &server_data, &server_data_length) &&
-        cti_connection_parse_done(connection))
-    {
-        dump_to_hex(service_data, service_data_length, service_data_buf, sizeof(service_data_buf));
-        dump_to_hex(server_data, server_data_length, server_data_buf, sizeof(server_data_buf));
-        syslog(LOG_INFO, "cti_service_add_parse: %" PRIu32 " %" PRIu16 "[ %s ] %" PRIu16 "[ %s ]",
-               enterprise_id, service_data_length, service_data_buf, server_data_length, server_data_buf);
-
-#ifndef POSIX_BUILD
-        status = ctiAddService(enterprise_id,
-                               service_data,
-                               service_data_length,
-                               server_data,
-                               server_data_length);
-
-#else
-        status = kCTIStatus_NoError;
-#endif
-        cti_send_response(connection, status);
-    }
-}
-
-static void
-cti_service_remove_parse(cti_connection_t connection)
-{
-    uint32_t enterprise_id;
-    uint16_t service_data_length;
-    int status = kCTIStatus_NoError;
-    void *service_data;
-    char service_data_buf[13];
-
-    // And statement will fail as soon as anything fails to parse.
-    if (cti_connection_u32_parse(connection, &enterprise_id) &&
-        cti_connection_data_parse(connection, &service_data, &service_data_length) &&
-        cti_connection_parse_done(connection))
-    {
-        dump_to_hex(service_data, service_data_length, service_data_buf, sizeof(service_data_buf));
-        syslog(LOG_INFO, "cti_service_add_parse: %" PRIu32 " %" PRIu16 "[ %s ]", enterprise_id, service_data_length, service_data_buf);
-
-#ifndef POSIX_BUILD
-        status = ctiRemoveService(enterprise_id,
-                                  service_data,
-                                  service_data_length);
-#else
-        status = kCTIStatus_NoError;
-#endif
-        cti_send_response(connection, status);
-    }
-}
-
-static void
-cti_prefix_add_parse(cti_connection_t connection)
-{
-    uint32_t preferred, valid;
-    uint8_t prefix_length;
-    struct in6_addr prefix;
-    void *prefix_data = NULL;
-    uint16_t prefix_data_length;
-    bool slaac, on_mesh, stable;
-    if (cti_connection_u32_parse(connection, &preferred) &&
-        cti_connection_u32_parse(connection, &valid) &&
-        cti_connection_data_parse(connection, &prefix_data, &prefix_data_length) &&
-        cti_connection_u8_parse(connection, &prefix_length) &&
-        cti_connection_bool_parse(connection, &slaac) &&
-        cti_connection_bool_parse(connection, &on_mesh) &&
-        cti_connection_bool_parse(connection, &stable) &&
-        cti_connection_parse_done(connection))
-    {
-        int status = kCTIStatus_NoError;
-        if (prefix_data_length != 8) {
-            status = kCTIStatus_Invalid;
-        } else {
-            in6prefix_copy_from_data(&prefix, prefix_data, prefix_data_length);
-#ifndef POSIX_BUILD
-            status = ctiAddMeshPrefix(&prefix, prefix_length, on_mesh, true, slaac, stable);
-#endif
-        }
-        cti_send_response(connection, status);
-    }
-    if (prefix_data != NULL) {
-        free(prefix_data);
-    }
-}
-
-static void
-cti_prefix_remove_parse(cti_connection_t connection)
-{
-    uint8_t prefix_length;
-    struct in6_addr prefix;
-    void *prefix_data = NULL;
-    uint16_t prefix_data_length;
-    if (cti_connection_data_parse(connection, &prefix_data, &prefix_data_length) &&
-        cti_connection_u8_parse(connection, &prefix_length) &&
-        cti_connection_parse_done(connection))
-    {
-        int status = kCTIStatus_NoError;
-        if (prefix_data_length != 8) {
-            status = kCTIStatus_Invalid;
-        } else {
-            in6prefix_copy_from_data(&prefix, prefix_data, prefix_data_length);
-#ifndef POSIX_BUILD
-            status = ctiRemoveMeshPrefix(&prefix, prefix_length);
-#endif
-            cti_send_response(connection, status);
-        }
-    }
-    if (prefix_data != NULL) {
-        free(prefix_data);
-    }
-}
-
-static void
-cti_get_tunnel_name_parse(cti_connection_t connection)
-{
-    if (cti_connection_parse_done(connection)) {
-#ifndef POSIX_BUILD
-        ctiRetrieveTunnel(connection);
-#endif
-    }
-}
-
-static void
-cti_message_parse(cti_connection_t connection)
-{
-    uint32_t propertyName;
-
-    cti_connection_parse_start(connection);
-    if (!cti_connection_u16_parse(connection, &connection->message_type)) {
-        return;
-    }
-    switch(connection->message_type) {
-    case kCTIMessageType_AddService:
-        cti_service_add_parse(connection);
-        break;
-    case kCTIMessageType_RemoveService:
-        cti_service_remove_parse(connection);
-        break;
-    case kCTIMessageType_AddPrefix:
-        cti_prefix_add_parse(connection);
-        break;
-    case kCTIMessageType_RemovePrefix:
-        cti_prefix_remove_parse(connection);
-        break;
-    case kCTIMessageType_GetTunnelName:
-        cti_get_tunnel_name_parse(connection);
-        break;
-    case kCTIMessageType_RequestStateEvents:
-        if (cti_connection_parse_done(connection)) {
-            connection->registered_event_flags |= CTI_EVENT_STATE;
-            cti_send_response(connection, kCTIStatus_NoError);
-#ifndef POSIX_BUILD
-            ctiRetrieveNodeType(connection, CTI_EVENT_STATE);
-
-#endif
-        }
-        break;
-    case kCTIMessageType_RequestUInt64PropEvents:
-        if (cti_connection_u32_parse(connection, &propertyName) && cti_connection_parse_done(connection)) {
-            cti_send_response(connection, kCTIStatus_NoError);
-#ifndef POSIX_BUILD
-            switch(propertyName) {
-            case kCTIPropertyPartitionID:
-                connection->registered_event_flags |= CTI_EVENT_PARTITION_ID;
-                ctiRetrievePartitionId(connection, CTI_EVENT_PARTITION_ID);
-                break;
-            case kCTIPropertyExtendedPANID:
-                connection->registered_event_flags |= CTI_EVENT_XPANID;
-                ctiRetrieveXPANID(connection, CTI_EVENT_XPANID);
-                break;
-            default:
-                cti_connection_close(connection);
-                break;
-            }
-#endif
-        }
-        break;
-    case kCTIMessageType_RequestRoleEvents:
-        if (cti_connection_parse_done(connection)) {
-            connection->registered_event_flags |= CTI_EVENT_ROLE;
-            cti_send_response(connection, kCTIStatus_NoError);
-#ifndef POSIX_BUILD
-            ctiRetrieveNodeType(connection, CTI_EVENT_ROLE);
-#endif
-        }
-        break;
-    case kCTIMessageType_RequestServiceEvents:
-        if (cti_connection_parse_done(connection)) {
-            connection->registered_event_flags |= CTI_EVENT_SERVICE;
-            cti_send_response(connection, kCTIStatus_NoError);
-#ifndef POSIX_BUILD
-            ctiRetrieveServiceList(connection, CTI_EVENT_SERVICE);
-#endif
-        }
-        break;
-    case kCTIMessageType_RequestPrefixEvents:
-        if (cti_connection_parse_done(connection)) {
-            connection->registered_event_flags |= CTI_EVENT_PREFIX;
-            cti_send_response(connection, kCTIStatus_NoError);
-#ifndef POSIX_BUILD
-            ctiRetrievePrefixList(connection, CTI_EVENT_PREFIX);
-#endif
-        }
-        break;
-    default:
-        cti_send_response(connection, kCTIStatus_Invalid);
-
-    }
-}
-
-static void
-cti_listen_callback(void)
-
-{
-    cti_connection_t connection;
-    int fd;
-    uid_t uid;
-    pid_t pid;
-
-    fd = cti_accept(cti_listener_fd, &uid, NULL, &pid);
-
-    // User is authenticated.
-    connection = cti_connection_allocate(100);
-    if (connection == NULL) {
-        close(fd);
-        return;
-    }
-    connection->fd = fd;
-    connection->next = connections;
-    connections = connection;
-    syslog(LOG_INFO, "cti_accept: connection from user %d, pid %d accepted", uid, pid);
-}
-
-int
-cti_init(void)
-{
-    cti_listener_fd = cti_make_unix_socket(CTI_SERVER_SOCKET_NAME, sizeof(CTI_SERVER_SOCKET_NAME), true);
-    if (cti_listener_fd == -1) {
-        return -1;
-    }
-    return 0;
-}
-
-void
-cti_fd_init(int *p_nfds, fd_set *r)
-{
-    int nfds = *p_nfds;
-    cti_connection_t connection, *p_connection;
-
-    if (cti_listener_fd >= nfds) {
-        nfds = cti_listener_fd + 1;
-    }
-    FD_SET(cti_listener_fd, r);
-
-    // GC any closed connections.
-    for (p_connection = &connections; *p_connection; ) {
-        connection = *p_connection;
-        if (connection->fd == -1) {
-            *p_connection = connection->next;
-            cti_connection_finalize(connection);
-        } else {
-            p_connection = &connection->next;
-        }
-    }
-
-    // Now process input on any connections that are still around.
-    for (connection = connections; connection; connection = connection->next) {
-        if (connection->fd >= nfds) {
-            nfds = connection->fd + 1;
-        }
-        FD_SET(connection->fd, r);
-    }
-    *p_nfds = nfds;
-}
-
-void
-cti_fd_process(fd_set *r)
-{
-    cti_connection_t connection;
-
-    if (FD_ISSET(cti_listener_fd, r)) {
-        cti_listen_callback();
-    }
-
-    for (connection = connections; connection; connection = connection->next) {
-        if (connection->fd != -1 && FD_ISSET(connection->fd, r)) {
-            cti_read(connection, cti_message_parse);
-        }
-    }
-}
-
-void
-cti_notify_event(unsigned int evt, send_event_t evt_handler)
-{
-    // Walk through connections and see if have registered for this particular event.
-    cti_connection_t connection = connections;
-    while (connection) {
-        if (evt & connection->registered_event_flags) {
-            evt_handler(connection, evt);
-        }
-        connection = connection->next;
-    }
-}
-
-#ifdef POSIX_BUILD
-int
-main(int argc, char **argv)
-{
-    fd_set fd_r, fd_w, fd_x;
-    int nfds = 0;
-
-    openlog("cti-server", LOG_PERROR, LOG_DAEMON);
-    signal(SIGPIPE, SIG_IGN); // because why ever?
-    cti_init();
-
-    do {
-        FD_ZERO(&fd_r);
-        FD_ZERO(&fd_w);
-        FD_ZERO(&fd_x);
-
-        cti_fd_init(&nfds, &fd_r);
-        syslog(LOG_INFO, "selecting: %d descriptors.", nfds);
-        if (select(nfds, &fd_r, &fd_w, &fd_x, NULL) < 0) {
-            syslog(LOG_ERR, "select: %s", strerror(errno));
-            exit(1);
-        }
-
-        cti_fd_process(&fd_r);
-    } while (1);
-}
-#endif
-
-// Local Variables:
-// mode: C
-// tab-width: 4
-// c-file-style: "bsd"
-// c-basic-offset: 4
-// fill-column: 108
-// indent-tabs-mode: nil
-// End:
diff --git a/ServiceRegistration/cti-services.c b/ServiceRegistration/cti-services.c
index ee038fb..dc53cdb 100644
--- a/ServiceRegistration/cti-services.c
+++ b/ServiceRegistration/cti-services.c
@@ -1,6 +1,6 @@
 /* cti-services.c
  *
- * Copyright (c) 2020-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2020-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -24,17 +24,18 @@
 #include <string.h>
 #include <stdlib.h>
 
-
-#include <errno.h>
-#include <unistd.h>
-#include <fcntl.h>
-#include <inttypes.h>
-#include "srp.h"
-#include "dns-msg.h"
-#include "ioloop.h"
+#include <Block.h>
+#include <os/log.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <arpa/inet.h>
+#include <netinet6/in6_var.h>
+#include <netinet/icmp6.h>
+#include <netinet6/nd6.h>
+#include "xpc_clients.h"
 #include "cti-services.h"
-
-static void cti_message_parse(cti_connection_t connection);
+typedef xpc_object_t object_t;
+typedef void (*cti_internal_callback_t)(cti_connection_t NONNULL conn_ref, object_t reply, cti_status_t status);
 
 
 //*************************************************************************************************************
@@ -42,129 +43,634 @@
 
 #include "cti-common.h"
 
-#include "cti-proto.h"
+static int client_serial_number;
 
+struct _cti_connection_t
+{
+    int ref_count;
 
-// For configuration comments, we return success/failure.
+    // Callback function ptr for Client
+    cti_callback_t callback;
+
+    // xpc_connection between client and daemon
+    xpc_connection_t NULLABLE connection;
+
+    // Before we can send commands, we have to check in, so when starting up, we stash the initial command
+    // here until we get an acknowledgment for the checkin.
+    object_t *first_command;
+
+    // Queue specified by client for scheduling its Callback
+    dispatch_queue_t NULLABLE client_queue;
+
+    // For commands that fetch properties and also track properties, this will contain the name of the property
+    // for which events are requested.
+    const char *property_name;
+
+    // For commands that fetch string properties, this will contain the name of the string property to look for
+    // in the answer
+    const char *return_property_name;
+
+    cti_internal_callback_t NONNULL internal_callback;
+
+    // Client context
+    void *NULLABLE context;
+
+    // Printed when debugging the event handler
+    const char *NONNULL command_name;
+
+    // This connection's serial number, based on the global client_serial_number above.
+    int serial;
+
+    // True if we've gotten a response to the check-in message.
+    bool checked_in;
+};
+
+//*************************************************************************************************************
+// Utility Functions
+
 static void
-cti_internal_reply_callback(cti_connection_t conn_ref, void *UNUSED object, cti_status_t status)
+cti_connection_finalize(cti_connection_t ref)
+{
+    if (ref->first_command != NULL) {
+        xpc_release(ref->first_command);
+        ref->first_command = NULL;
+    }
+    free(ref);
+}
+
+#define cti_connection_release(ref) cti_connection_release_(ref, __FILE__, __LINE__)
+static void
+cti_connection_release_(cti_connection_t ref, const char *file, int line)
+{
+    ref->callback.reply = NULL;
+    RELEASE(ref, cti_connection);
+}
+
+static void
+cti_xpc_connection_finalize(void *context)
+{
+    cti_connection_t ref = context;
+    INFO("[CX%d] " PUB_S_SRP, ref->serial, ref->command_name);
+    cti_connection_release(context);
+}
+
+static char *
+cti_xpc_copy_description(object_t object)
+{
+    xpc_type_t type = xpc_get_type(object);
+    if (type == XPC_TYPE_UINT64) {
+        uint64_t num = xpc_uint64_get_value(object);
+        char buf[23];
+        snprintf(buf, sizeof buf, "%llu", num);
+        return strdup(buf);
+    } else if (type == XPC_TYPE_INT64) {
+        int64_t num = xpc_int64_get_value(object);
+        char buf[23];
+        snprintf(buf, sizeof buf, "%lld", num);
+        return strdup(buf);
+    } else if (type == XPC_TYPE_STRING) {
+        const char *str = xpc_string_get_string_ptr(object);
+        size_t len = xpc_string_get_length(object);
+        char *ret = malloc(len + 3);
+        if (ret != NULL) {
+            *ret = '"';
+            strlcpy(ret + 1, str, len + 1);
+            ret[len + 1] = '"';
+            ret[len + 2] = 0;
+            return ret;
+        }
+    } else if (type == XPC_TYPE_DATA) {
+        const uint8_t *data = xpc_data_get_bytes_ptr(object);
+        size_t i, len = xpc_data_get_length(object);
+        char *ret = malloc(len * 2 + 3);
+        if (ret != NULL) {
+            ret[0] = '0';
+            ret[1] = 'x';
+            for (i = 0; i < len; i++) {
+                snprintf(ret + i * 2, 3, "%02x", data[i]);
+            }
+            return ret;
+        }
+    } else if (type == XPC_TYPE_BOOL) {
+        bool flag = xpc_bool_get_value(object);
+        if (flag) {
+            return strdup("true");
+        } else {
+            return strdup("false");
+        }
+    } else if (type == XPC_TYPE_ARRAY) {
+        size_t avail, vlen, len = 0, i, count = xpc_array_get_count(object);
+        char **values = malloc(count * sizeof(*values));
+        char *ret, *p_ret;
+        if (values == NULL) {
+            return NULL;
+        }
+        xpc_array_apply(object, ^bool (size_t index, object_t value) {
+                values[index] = cti_xpc_copy_description(value);
+                return true;
+            });
+        for (i = 0; i < count; i++) {
+            if (values[i] == NULL) {
+                len += 6;
+            } else {
+                len += strlen(values[i]) + 2;
+            }
+        }
+        ret = malloc(len + 3);
+        p_ret = ret;
+        avail = len + 1;
+        *p_ret++ = '[';
+        --avail;
+        for (i = 0; i < count; i++) {
+            if (p_ret != NULL) {
+                snprintf(p_ret, avail, "%s%s%s", i == 0 ? "" : " ", values[i] != NULL ? values[i] : "NULL", (i + 1 == count) ? "" : ",");
+                vlen = strlen(p_ret);
+                p_ret += vlen;
+                avail -= vlen;
+            }
+            if (values[i] != NULL) {
+                free(values[i]);
+            }
+        }
+        *p_ret++ = ']';
+        *p_ret++ = 0;
+        free(values);
+        return ret;
+    }
+    return xpc_copy_description(object);
+}
+
+static void
+cti_log_object(const char *context, int serial, const char *command, const char *preamble, const char *divide, object_t *object, char *indent)
+{
+    xpc_type_t type = xpc_get_type(object);
+    static char no_indent[] = "";
+    if (indent == NULL) {
+        indent = no_indent;
+    }
+    char *new_indent;
+    size_t depth;
+    char *desc;
+    char *compound_begin;
+    char *compound_end;
+
+    if (type == XPC_TYPE_DICTIONARY || type == XPC_TYPE_ARRAY) {
+        bool compact = true;
+        bool *p_compact = &compact;
+        if (type == XPC_TYPE_DICTIONARY) {
+            compound_begin = "{";
+            compound_end = "}";
+            xpc_dictionary_apply(object, ^bool (const char *__unused key, object_t value) {
+                    xpc_type_t sub_type = xpc_get_type(value);
+                    if (sub_type == XPC_TYPE_DICTIONARY) {
+                        *p_compact = false;
+                    } else if (sub_type == XPC_TYPE_ARRAY) {
+                        xpc_array_apply(value, ^bool (size_t __unused index, object_t sub_value) {
+                                xpc_type_t sub_sub_type = xpc_get_type(sub_value);
+                                if (sub_sub_type == XPC_TYPE_DICTIONARY || sub_sub_type == XPC_TYPE_ARRAY) {
+                                    *p_compact = false;
+                                }
+                                return true;
+                            });
+                    }
+                    return true;
+                });
+        } else {
+            compound_begin = "[";
+            compound_end = "]";
+            xpc_array_apply(object, ^bool (size_t __unused index, object_t value) {
+                    xpc_type_t sub_type = xpc_get_type(value);
+                    if (sub_type == XPC_TYPE_DICTIONARY || sub_type == XPC_TYPE_ARRAY) {
+                        *p_compact = false;
+                    }
+                    return true;
+                });
+        }
+        if (compact) {
+            size_t i, count;
+            const char **keys = NULL;
+            char **values;
+            char linebuf[160], *p_space;
+            size_t space_avail = sizeof(linebuf);
+            bool first = true;
+
+            if (type == XPC_TYPE_DICTIONARY) {
+                count = xpc_dictionary_get_count(object);
+            } else {
+                count = xpc_array_get_count(object);
+            }
+
+            values = malloc(count * sizeof(*values));
+            if (values == NULL) {
+                INFO("[CX%d] no memory", serial);
+                return;
+            }
+            if (type == XPC_TYPE_DICTIONARY) {
+                int index = 0, *p_index = &index;
+                keys = malloc(count * sizeof(*keys));
+                if (keys == NULL) {
+                    free(values);
+                    INFO("[CX%d] no memory", serial);
+                }
+                xpc_dictionary_apply(object, ^bool (const char *key, object_t value) {
+                        values[*p_index] = cti_xpc_copy_description(value);
+                        keys[*p_index] = key;
+                        (*p_index)++;
+                        return true;
+                    });
+            } else {
+                xpc_array_apply(object, ^bool (size_t index, object_t value) {
+                        values[index] = cti_xpc_copy_description(value);
+                        return true;
+                    });
+            }
+            p_space = linebuf;
+            for (i = 0; i < count; i++) {
+                char *str = values[i];
+                size_t len;
+                char *eol = "";
+                bool emitted = false;
+                if (str == NULL) {
+                    str = "NULL";
+                    len = 6;
+                } else {
+                    len = strlen(str) + 2;
+                }
+                if (type == XPC_TYPE_DICTIONARY) {
+#ifdef __clang_analyzer__
+                    len = 2;
+#else
+                    len += strlen(keys[i]) + 2; // "key: "
+#endif
+                }
+                if (len + 1 > space_avail) {
+                    if (i + 1 == count) {
+                        eol = compound_end;
+                    }
+                    if (space_avail != sizeof(linebuf)) {
+                        if (first) {
+                            INFO("[CX%d] " PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " " PUB_S_SRP
+                                 PUB_S_SRP PUB_S_SRP, serial, context, command,
+                                 indent, preamble, divide, compound_begin, linebuf, eol);
+                            first = false;
+                        } else {
+                            INFO("[CX%d] " PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " +" PUB_S_SRP
+                                 PUB_S_SRP, serial, context, command,
+                                 indent, preamble, divide, linebuf, eol);
+                        }
+                        space_avail = sizeof linebuf;
+                        p_space = linebuf;
+                    }
+                    if (len + 1 > space_avail) {
+                        if (type == XPC_TYPE_DICTIONARY) {
+#ifndef __clang_analyzer__
+                            if (first) {
+                                INFO("[CX%d] " PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " " PUB_S_SRP
+                                     PUB_S_SRP ": " PUB_S_SRP PUB_S_SRP, serial, context, command,
+                                     indent, preamble, divide, compound_begin, keys[i], str, eol);
+                                first = false;
+                            } else {
+                                INFO("[CX%d] " PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " +" PUB_S_SRP
+                                     ": " PUB_S_SRP PUB_S_SRP, serial, context, command,
+                                     indent, preamble, divide, keys[i], str, eol);
+                            }
+#endif
+                        } else {
+                            if (first) {
+                                INFO("[CX%d] " PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " " PUB_S_SRP
+                                     PUB_S_SRP PUB_S_SRP, serial, context, command,
+                                     indent, preamble, divide, compound_begin, str, eol);
+                                first = false;
+                            } else {
+                                INFO("[CX%d] " PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " +" PUB_S_SRP
+                                     PUB_S_SRP, serial, context, command, indent, preamble, divide, str, eol);
+                            }
+                        }
+                        emitted = true;
+                    }
+                }
+                if (!emitted) {
+                    if (type == XPC_TYPE_DICTIONARY) {
+#ifndef __clang_analyzer__
+                        snprintf(p_space, space_avail, "%s%s: %s%s", i == 0 ? "" : " ", keys[i], str, i + 1 == count ? "" : ",");
+#endif
+                    } else {
+                        snprintf(p_space, space_avail, "%s%s%s", i == 0 ? "" : " ", str, i + 1 == count ? "" : ",");
+                    }
+                    len = strlen(p_space);
+                    p_space += len;
+                    space_avail -= len;
+                }
+                if (values[i] != NULL) {
+                    free(values[i]);
+                    values[i] = NULL;
+                }
+            }
+            if (linebuf != p_space) {
+                if (first) {
+                    INFO("[CX%d] " PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " " PUB_S_SRP PUB_S_SRP
+                         PUB_S_SRP, serial, context, command,
+                         indent, preamble, divide, compound_begin, linebuf, compound_end);
+                } else {
+                    INFO("[CX%d] " PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " + " PUB_S_SRP PUB_S_SRP,
+                         serial, context, command, indent, preamble, divide, linebuf, compound_end);
+                }
+            }
+            free(values);
+            if (keys != NULL) {
+                free(keys);
+            }
+        } else {
+            depth = strlen(indent);
+            new_indent = malloc(depth + 3);
+            if (new_indent == NULL) {
+                new_indent = indent;
+            } else {
+                memset(new_indent, ' ', depth + 2);
+                new_indent[depth + 2] = 0;
+            }
+            if (type == XPC_TYPE_DICTIONARY) {
+                xpc_dictionary_apply(object, ^bool (const char *key, object_t value) {
+                    cti_log_object(context, serial, command, key, ": ", value, new_indent);
+                    return true;
+                });
+            } else {
+                xpc_array_apply(object, ^bool (size_t index, object_t value) {
+                    char numbuf[23];
+                    snprintf(numbuf, sizeof(numbuf), "%zd", index);
+                    cti_log_object(context, serial, command, numbuf, ": ", value, new_indent);
+                    return true;
+                });
+            }
+            if (new_indent != indent) {
+                free(new_indent);
+            }
+        }
+    } else {
+        desc = cti_xpc_copy_description(object);
+        INFO("[CX%d] " PUB_S_SRP "(" PUB_S_SRP "): " PUB_S_SRP PUB_S_SRP PUB_S_SRP " " PUB_S_SRP,
+             serial, context, command, indent, preamble, divide, desc);
+        free(desc);
+    }
+}
+
+static void
+cti_event_handler(object_t event, cti_connection_t conn_ref)
+{
+    if (event == XPC_ERROR_CONNECTION_INVALID) {
+        INFO("[CX%d] (" PUB_S_SRP "): cleanup", conn_ref->serial, conn_ref->command_name);
+        if (conn_ref->callback.reply != NULL) {
+            conn_ref->internal_callback(conn_ref, event, kCTIStatus_Disconnected);
+        } else {
+            INFO("[CX%d] No callback", conn_ref->serial);
+        }
+        if (conn_ref->connection != NULL) {
+            INFO("[CX%d] releasing connection %p", conn_ref->serial, conn_ref->connection);
+            xpc_release(conn_ref->connection);
+            conn_ref->connection = NULL;
+        }
+        return;
+    }
+
+    if (conn_ref->connection == NULL) {
+        cti_log_object("cti_event_handler NULL connection",
+                       conn_ref->serial, conn_ref->command_name, "", "", event, "");
+        return;
+    }
+
+    if (xpc_get_type(event) == XPC_TYPE_DICTIONARY) {
+        cti_log_object("cti_event_handler", conn_ref->serial, conn_ref->command_name, "", "", event, "");
+        if (!conn_ref->checked_in) {
+            object_t command_result = xpc_dictionary_get_value(event, "commandResult");
+            int status = 0;
+            if (command_result != NULL) {
+                status = (int)xpc_int64_get_value(command_result);
+                if (status == 0) {
+                    object_t command_data = xpc_dictionary_get_value(event, "commandData");
+                    if (command_data == NULL) {
+                        status = 0;
+                    } else {
+                        object_t ret_value = xpc_dictionary_get_value(command_data, "ret");
+                        if (ret_value == NULL) {
+                            status = 0;
+                        } else {
+                            status = (int)xpc_int64_get_value(ret_value);
+                        }
+                    }
+                }
+            }
+
+            if (status != 0) {
+                conn_ref->internal_callback(conn_ref, event, kCTIStatus_UnknownError);
+                INFO("[CX%d] canceling xpc connection %p", conn_ref->serial, conn_ref->connection);
+                xpc_connection_cancel(conn_ref->connection);
+            } else if (conn_ref->property_name != NULL) {
+                // We're meant to both get the property and subscribe to events on it.
+                object_t *dict = xpc_dictionary_create(NULL, NULL, 0);
+                if (dict == NULL) {
+                    ERROR("[CX%d] cti_event_handler(" PUB_S_SRP "): no memory, canceling %p.",
+                          conn_ref->serial, conn_ref->command_name, conn_ref->connection);
+                    xpc_connection_cancel(conn_ref->connection);
+                } else {
+                    object_t *array = xpc_array_create(NULL, 0);
+                    if (array == NULL) {
+                        ERROR("[CX%d] cti_event_handler(" PUB_S_SRP "): no memory, canceling %p.",
+                              conn_ref->serial, conn_ref->command_name, conn_ref->connection);
+                        xpc_connection_cancel(conn_ref->connection);
+                    } else {
+                        xpc_dictionary_set_string(dict, "command", "eventsOn");
+                        xpc_dictionary_set_string(dict, "clientName", "srp-mdns-proxy");
+                        xpc_dictionary_set_value(dict, "eventList", array);
+                        xpc_array_set_string(array, XPC_ARRAY_APPEND, conn_ref->property_name);
+                        conn_ref->property_name = NULL;
+                        cti_log_object("cti_event_handler/events on",
+                                       conn_ref->serial, conn_ref->command_name, "", "", dict, "");
+                        INFO("[CX%d] sending message on connection %p", conn_ref->serial, conn_ref->connection);
+                        xpc_connection_send_message_with_reply(conn_ref->connection, dict, conn_ref->client_queue,
+                                                               ^(object_t in_event) {
+                                                                   cti_event_handler(in_event, conn_ref);
+                                                               });
+                        xpc_release(array);
+                    }
+                    xpc_release(dict);
+                }
+            } else {
+                object_t *message = conn_ref->first_command;
+                conn_ref->first_command = NULL;
+                cti_log_object("cti_event_handler/command is",
+                               conn_ref->serial, conn_ref->command_name, "", "", message, "");
+                conn_ref->checked_in = true;
+
+                INFO("[CX%d] sending message on connection %p", conn_ref->serial, conn_ref->connection);
+                xpc_connection_send_message_with_reply(conn_ref->connection, message, conn_ref->client_queue,
+                                                       ^(object_t in_event) {
+                                                           cti_event_handler(in_event, conn_ref);
+                                                       });
+                xpc_release(message);
+            }
+        } else {
+            conn_ref->internal_callback(conn_ref, event, kCTIStatus_NoError);
+        }
+    } else {
+        cti_log_object("cti_event_handler/other", conn_ref->serial, conn_ref->command_name, "", "", event, "");
+        ERROR("[CX%d] cti_event_handler: Unexpected Connection Error [" PUB_S_SRP "]",
+              conn_ref->serial, xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION));
+        conn_ref->internal_callback(conn_ref, NULL, kCTIStatus_DaemonNotRunning);
+        if (event != XPC_ERROR_CONNECTION_INTERRUPTED) {
+            INFO("[CX%d] canceling xpc connection %p", conn_ref->serial, conn_ref->connection);
+            xpc_connection_cancel(conn_ref->connection);
+        }
+    }
+}
+
+// Creates a new cti_ Connection Reference(cti_connection_t)
+static cti_status_t
+init_connection(cti_connection_t *ref, const char *servname, object_t *dict, const char *command_name,
+                const char *property_name, const char *return_property_name, void *context, cti_callback_t app_callback,
+                cti_internal_callback_t internal_callback, run_context_t client_queue,
+                const char *file, int line)
+{
+    // Use an cti_connection_t on the stack to be captured in the blocks below, rather than
+    // capturing the cti_connection_t* owned by the client
+#ifdef MALLOC_DEBUG_LOGGING
+    cti_connection_t conn_ref = debug_calloc(1, sizeof(struct _cti_connection_t), file, line);
+#else
+    cti_connection_t conn_ref = calloc(1, sizeof(struct _cti_connection_t));
+#endif
+    if (conn_ref == NULL) {
+        ERROR("no memory to allocate!");
+        return kCTIStatus_NoMemory;
+    }
+    conn_ref->serial = client_serial_number;
+    client_serial_number++;
+
+    // We always retain a reference for the caller, even if the caller doesn't actually hold the reference.
+    // Calls that do not result in repeated callbacks release this reference after calling the callback.
+    // Such calls do not return a reference to the caller, so there is no chance of a double release.
+    // Calls that result in repeated callbacks have to release the reference by calling cti_events_discontinue.
+    // If this isn't done, the reference will never be released.
+    RETAIN(conn_ref, cti_connection);
+
+    if (client_queue == NULL) {
+        client_queue = dispatch_get_main_queue();
+    }
+
+    // Initialize the cti_connection_t
+    dispatch_retain(client_queue);
+    conn_ref->command_name = command_name;
+    conn_ref->property_name = property_name;
+    conn_ref->return_property_name = return_property_name;
+    conn_ref->context = context;
+    conn_ref->client_queue = client_queue;
+    conn_ref->callback = app_callback;
+    conn_ref->internal_callback = internal_callback;
+    conn_ref->connection = xpc_connection_create_mach_service(servname, conn_ref->client_queue,
+                                                              XPC_CONNECTION_MACH_SERVICE_PRIVILEGED);
+    INFO("[CX%d] xpc connection: %p", conn_ref->serial, conn_ref->connection);
+    conn_ref->first_command = dict;
+    xpc_retain(dict);
+
+    cti_log_object("init_connection/command", conn_ref->serial, conn_ref->command_name, "", "", dict, "");
+
+    if (conn_ref->connection == NULL)
+    {
+        ERROR("conn_ref/lib_q is NULL");
+        if (conn_ref != NULL) {
+            RELEASE_HERE(conn_ref, cti_connection);
+        }
+        return kCTIStatus_NoMemory;
+    }
+
+    RETAIN_HERE(conn_ref, cti_connection); // For the event handler.
+    xpc_connection_set_event_handler(conn_ref->connection, ^(object_t event) { cti_event_handler(event, conn_ref); });
+    xpc_connection_set_finalizer_f(conn_ref->connection, cti_xpc_connection_finalize);
+    xpc_connection_set_context(conn_ref->connection, conn_ref);
+    xpc_connection_resume(conn_ref->connection);
+
+    char srp_name[] = "srp-mdns-proxy";
+    char client_name[sizeof(srp_name) + 20];
+    snprintf(client_name, sizeof client_name, "%s-%d", srp_name, conn_ref->serial);
+
+    object_t checkin_command = xpc_dictionary_create(NULL, NULL, 0);
+
+    xpc_dictionary_set_string(checkin_command, "command", "checkIn");
+    xpc_dictionary_set_string(checkin_command, "clientName", client_name);
+
+    cti_log_object("init_connection/checkin", conn_ref->serial, conn_ref->command_name, "", "", checkin_command, "");
+    INFO("[CX%d] sending message on connection %p", conn_ref->serial, conn_ref->connection);
+    xpc_connection_send_message_with_reply(conn_ref->connection, checkin_command, conn_ref->client_queue,
+                                           ^(object_t event) { cti_event_handler(event, conn_ref); });
+
+    xpc_release(checkin_command);
+    if (ref) {
+        *ref = conn_ref;
+    }
+    return kCTIStatus_NoError;
+}
+
+static cti_status_t
+setup_for_command(cti_connection_t *ref, run_context_t client_queue, const char *command_name,
+                  const char *property_name, const char *return_property_name, object_t dict, const char *command,
+                  void *context, cti_callback_t app_callback, cti_internal_callback_t internal_callback,
+                  bool events_only, const char *file, int line)
+{
+    cti_status_t errx = kCTIStatus_NoError;
+
+    // Sanity Checks
+    if (app_callback.reply == NULL || internal_callback == NULL)
+    {
+        ERROR(PUB_S_SRP ": NULL cti_connection_t OR Callback OR Client_Queue parameter", command_name);
+        return kCTIStatus_BadParam;
+    }
+
+    // Get conn_ref from init_connection()
+    if (events_only) {
+        xpc_dictionary_set_string(dict, "command", "eventsOn");
+        object_t *array = xpc_array_create(NULL, 0);
+        if (array != NULL) {
+            xpc_array_set_string(array, XPC_ARRAY_APPEND, property_name);
+            xpc_dictionary_set_value(dict, "eventList", array);
+            property_name = NULL;
+            xpc_release(array);
+        } else {
+            return kCTIStatus_NoMemory;
+        }
+    } else {
+        xpc_dictionary_set_string(dict, "command", command);
+    }
+
+    errx = init_connection(ref, "com.apple.wpantund.xpc", dict, command_name, property_name, return_property_name,
+                           context, app_callback, internal_callback, client_queue, file, line);
+    if (errx) // On error init_connection() leaves *conn_ref set to NULL
+    {
+        ERROR(PUB_S_SRP ": Since init_connection() returned %d error returning w/o sending msg", command_name, errx);
+        return errx;
+    }
+
+    return errx;
+}
+
+static void
+cti_internal_event_reply_callback(cti_connection_t NONNULL conn_ref, object_t __unused reply, cti_status_t status)
 {
     cti_reply_t callback;
-    INFO("conn_ref = %p", conn_ref);
+    INFO("[CX%d] conn_ref = %p", conn_ref != NULL ? conn_ref->serial : 0, conn_ref);
     callback = conn_ref->callback.reply;
     if (callback != NULL) {
         callback(conn_ref->context, status);
-        // We only ever call this callback once.
-        conn_ref->callback.reply = NULL;
-    }
-    cti_connection_close(conn_ref);
-}
-
-static void
-cti_fd_finalize(void *context)
-{
-    cti_connection_t connection = context;
-    connection->io_context = NULL;
-    if (connection->callback.reply != NULL && connection->internal_callback != NULL) {
-        connection->internal_callback(connection, NULL, kCTIStatus_Disconnected);
-    }
-    RELEASE_HERE(connection, cti_connection);
-}
-
-void
-cti_connection_close(cti_connection_t connection)
-{
-    // The reason we test for NULL here is to save some typing: when a connection is closed remotely, we have call the
-    // internal event handler for the event; this event handler closes the connection when we've just received a
-    // successful reply. However, when the remote end closes the connection without that reply having been processed, we
-    // get to the internal callback with connection->io_context set to NULL. Rather than checking in every event handler,
-    // it's easier to check here.
-    if (connection->io_context != NULL) {
-        ioloop_close(connection->io_context);
-        ioloop_file_descriptor_release(connection->io_context);
-        connection->io_context = NULL;
     }
 }
 
 static void
-cti_response_parse(cti_connection_t connection)
+cti_internal_reply_callback(cti_connection_t NONNULL conn_ref, object_t reply, cti_status_t status)
 {
-    uint16_t responding_to;
-    int32_t status;
-
-    // And statement will fail as soon as anything fails to parse.
-    if (cti_connection_u16_parse(connection, &responding_to) &&
-        cti_connection_i32_parse(connection, &status) &&
-        cti_connection_parse_done(connection))
-    {
-        INFO("%d %d", responding_to, status);
-        connection->internal_callback(connection, NULL, status);
+    cti_internal_event_reply_callback(conn_ref, reply, status);
+    conn_ref->callback.reply = NULL;
+    if (conn_ref->connection != NULL) {
+        INFO("[CX%d] canceling connection %p", conn_ref->serial, conn_ref->connection);
+        xpc_connection_cancel(conn_ref->connection);
     }
-}
-
-static void
-cti_tunnel_response_parse(cti_connection_t connection)
-{
-    char *tunnel_name = NULL;
-
-    // And statement will fail as soon as anything fails to parse.
-    if (cti_connection_string_parse(connection, &tunnel_name) &&
-        cti_connection_parse_done(connection))
-    {
-        INFO("%s", tunnel_name);
-        connection->internal_callback(connection, tunnel_name, kCTIStatus_NoError);
-    }
-    if (tunnel_name != NULL) {
-        free(tunnel_name);
-    }
-}
-
-static void
-cti_connection_read_callback(io_t *UNUSED io, void *context)
-{
-    cti_connection_t connection = context;
-
-    cti_read(connection, cti_message_parse);
-}
-
-void
-cti_connection_release_(cti_connection_t connection, const char *file, int line)
-{
-    RELEASE(connection, cti_connection);
-}
-
-static int
-cti_connection_create(void *context, cti_callback_t callback,
-                      cti_internal_callback_t internal_callback, cti_connection_t *retcon)
-{
-    cti_connection_t connection = cti_connection_allocate(500);
-    if (connection == NULL) {
-        ERROR("cti_connection_create: no memory for connection.");
-        return kCTIStatus_NoMemory;
-    }
-    RETAIN_HERE(connection, cti_connection);
-
-    connection->fd = cti_make_unix_socket(CTI_SERVER_SOCKET_NAME, sizeof(CTI_SERVER_SOCKET_NAME), false);
-    if (connection->fd < 0) {
-        int ret = errno == ECONNREFUSED ? kCTIStatus_DaemonNotRunning : EPERM ? kCTIStatus_NotPermitted : kCTIStatus_UnknownError;
-        ERROR("cti_connection_create: socket: %s", strerror(errno));
-        cti_connection_release(connection);
-        return ret;
-    }
-
-    connection->io_context = ioloop_file_descriptor_create(connection->fd, connection, cti_fd_finalize);
-    if (connection->io_context == NULL) {
-        ERROR("cti_connection_create: can't create file descriptor object.");
-        close(connection->fd);
-        cti_connection_release(connection);
-        return kCTIStatus_NoMemory;
-    }
-    ioloop_add_reader(connection->io_context, cti_connection_read_callback);
-    connection->context = context;
-    connection->callback = callback;
-    connection->internal_callback = internal_callback;
-    *retcon = connection;
-    return kCTIStatus_NoError;
+    cti_connection_release(conn_ref);
 }
 
 cti_status_t
@@ -174,29 +680,24 @@
 {
     cti_callback_t app_callback;
     app_callback.reply = callback;
-    int ret;
-    cti_connection_t conn_ref;
-    ret = cti_connection_create(context, app_callback, cti_internal_reply_callback, &conn_ref);
-    if (ret == kCTIStatus_NoError) {
-        conn_ref->callback.reply = callback;
-        if (cti_connection_message_create(conn_ref, kCTIMessageType_AddService,
-                                          // sizeof(u32) + sizeof(length) + sizeof(length) = 8
-                                          8 + service_data_length + server_data_length) &&
-            cti_connection_u32_put(conn_ref, enterprise_number) &&
-            cti_connection_data_put(conn_ref, service_data, service_data_length) &&
-            cti_connection_data_put(conn_ref, server_data, server_data_length))
-        {
-            if (!cti_connection_message_send(conn_ref)) {
-                ret = kCTIStatus_Disconnected;
-            }
-        } else {
-            ret = kCTIStatus_NoMemory;
-        }
-        if (ret != kCTIStatus_NoError) {
-            cti_connection_close(conn_ref);
-        }
+    cti_status_t errx;
+    object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    xpc_dictionary_set_data(dict, "service_data", service_data, service_data_length);
+    if (server_data != NULL) {
+        xpc_dictionary_set_data(dict, "server_data", server_data, server_data_length);
     }
-    return ret;
+    xpc_dictionary_set_uint64(dict, "enterprise_number", enterprise_number);
+    xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
+    xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
+    xpc_dictionary_set_string(dict, "method", "ServiceAdd");
+    xpc_dictionary_set_bool(dict, "stable", true);
+
+    errx = setup_for_command(NULL, client_queue, "add_service", NULL, NULL, dict, "WpanctlCmd",
+                             context, app_callback, cti_internal_reply_callback, false, file, line);
+    xpc_release(dict);
+
+    return errx;
 }
 
 cti_status_t
@@ -206,63 +707,72 @@
 {
     cti_callback_t app_callback;
     app_callback.reply = callback;
-    int ret;
-    cti_connection_t conn_ref;
-    ret = cti_connection_create(context, app_callback, cti_internal_reply_callback, &conn_ref);
-    if (ret == kCTIStatus_NoError) {
-        if (cti_connection_message_create(conn_ref, kCTIMessageType_RemoveService,
-                                          // sizeof(u32) + sizeof(length) = 6
-                                          6 + service_data_length) &&
-            cti_connection_u32_put(conn_ref, enterprise_number) &&
-            cti_connection_data_put(conn_ref, service_data, service_data_length))
-        {
-            if (!cti_connection_message_send(conn_ref)) {
-                ret = kCTIStatus_Disconnected;
-            }
-        } else {
-            ret = kCTIStatus_NoMemory;
-        }
-        if (ret != kCTIStatus_NoError) {
-            cti_connection_close(conn_ref);
-        }
-    }
-    return ret;
+    cti_status_t errx;
+    object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    xpc_dictionary_set_data(dict, "service_data", service_data, service_data_length);
+    xpc_dictionary_set_uint64(dict, "enterprise_number", enterprise_number);
+    xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
+    xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
+    xpc_dictionary_set_string(dict, "method", "ServiceRemove");
+
+    errx = setup_for_command(NULL, client_queue, "remove_service", NULL, NULL, dict, "WpanctlCmd",
+                             context, app_callback, cti_internal_reply_callback, false, file, line);
+    xpc_release(dict);
+
+    return errx;
 }
 
+static cti_status_t
+cti_do_prefix(void *context, cti_reply_t callback, run_context_t client_queue,
+              struct in6_addr *prefix, int prefix_length, bool on_mesh, bool preferred, bool slaac, bool stable, bool adding,
+              int priority, const char *file, int line)
+{
+    cti_callback_t app_callback;
+    app_callback.reply = callback;
+    cti_status_t errx;
+    object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    if (dict == NULL) {
+        ERROR("cti_do_prefix: no memory for command dictionary.");
+        return kCTIStatus_NoMemory;
+    }
+    xpc_dictionary_set_bool(dict, "preferred", preferred);
+    if (adding) {
+        xpc_dictionary_set_uint64(dict, "preferredLifetime", ND6_INFINITE_LIFETIME);
+        xpc_dictionary_set_uint64(dict, "validLifetime", ND6_INFINITE_LIFETIME);
+    } else {
+        xpc_dictionary_set_uint64(dict, "preferredLifetime", 0);
+        xpc_dictionary_set_uint64(dict, "validLifetime", 0);
+    }
+    xpc_dictionary_set_int64(dict, "prefix_length", 16);
+    xpc_dictionary_set_bool(dict, "dhcp", false);
+    xpc_dictionary_set_data(dict, "prefix", prefix, sizeof(*prefix));
+    xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
+    xpc_dictionary_set_uint64(dict, "prefix_len_in_bits", prefix_length);
+    xpc_dictionary_set_bool(dict, "slaac", slaac);
+    xpc_dictionary_set_bool(dict, "onMesh", on_mesh);
+    xpc_dictionary_set_bool(dict, "configure", false);
+    xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
+    xpc_dictionary_set_string(dict, "method", "ConfigGateway");
+    xpc_dictionary_set_bool(dict, "stable", stable);
+    xpc_dictionary_set_bool(dict, "defaultRoute", true);
+    xpc_dictionary_set_int64(dict, "priority", priority);
+
+    errx = setup_for_command(NULL, client_queue, adding ? "add_prefix" : "remove_prefix", NULL, NULL, dict, "WpanctlCmd",
+                             context, app_callback, cti_internal_reply_callback, false, file, line);
+    xpc_release(dict);
+
+    return errx;
+}
 
 cti_status_t
 cti_add_prefix_(srp_server_t *UNUSED server, void *context, cti_reply_t callback, run_context_t client_queue,
                 struct in6_addr *prefix, int prefix_length, bool on_mesh, bool preferred, bool slaac, bool stable,
                 int priority, const char *file, int line)
 {
-    cti_callback_t app_callback;
-    int ret;
-    cti_connection_t conn_ref;
-    app_callback.reply = callback;
-    ret = cti_connection_create(context, app_callback, cti_internal_reply_callback, &conn_ref);
-    if (ret == kCTIStatus_NoError) {
-        if (cti_connection_message_create(conn_ref, kCTIMessageType_AddPrefix,
-                                          // sizeof(u32) * 2 + sizeof(prefix) (8) + sizeof(u8) + 3 * sizeof(bool) = 20
-                                          20) &&
-            cti_connection_u32_put(conn_ref, ND6_INFINITE_LIFETIME) &&
-            cti_connection_u32_put(conn_ref, ND6_INFINITE_LIFETIME) &&
-            cti_connection_data_put(conn_ref, prefix, 8) &&
-            cti_connection_u8_put(conn_ref, prefix_length) &&
-            cti_connection_bool_put(conn_ref, slaac) &&
-            cti_connection_bool_put(conn_ref, on_mesh) &&
-            cti_connection_bool_put(conn_ref, stable))
-        {
-            if (!cti_connection_message_send(conn_ref)) {
-                ret = kCTIStatus_Disconnected;
-            }
-        } else {
-            ret = kCTIStatus_NoMemory;
-        }
-        if (ret != kCTIStatus_NoError) {
-            cti_connection_close(conn_ref);
-        }
-    }
-    return ret;
+    return cti_do_prefix(context, callback, client_queue, prefix, prefix_length, on_mesh, preferred, slaac, stable,
+                         true, priority, file, line);
 }
 
 cti_status_t
@@ -270,44 +780,149 @@
                    run_context_t NULLABLE client_queue, struct in6_addr *NONNULL prefix, int prefix_length,
                    const char *file, int line)
 {
-    cti_callback_t app_callback;
-    int ret;
-    cti_connection_t conn_ref;
-    app_callback.reply = callback;
-    ret = cti_connection_create(context, app_callback, cti_internal_reply_callback, &conn_ref);
-    if (ret == kCTIStatus_NoError) {
-        if (cti_connection_message_create(conn_ref, kCTIMessageType_RemovePrefix,
-                                          // sizeof(prefix) (8) + sizeof(u8) = 9
-                                          9) &&
-            cti_connection_data_put(conn_ref, prefix, 8) &&
-            cti_connection_u8_put(conn_ref, prefix_length))
-        {
-            if (!cti_connection_message_send(conn_ref)) {
-                ret = kCTIStatus_Disconnected;
-            }
-        } else {
-            ret = kCTIStatus_NoMemory;
-        }
-        if (ret != kCTIStatus_NoError) {
-            cti_connection_close(conn_ref);
-        }
-    }
-    return ret;
+    return cti_do_prefix(context, callback, client_queue, prefix, prefix_length, false, false, false, false, false, 0,
+                         file, line);
 }
 
-// For configuration comments, we return success/failure.
-static void
-cti_internal_string_property_reply(cti_connection_t conn_ref, void *tunnel_name, cti_status_t status)
+cti_status_t
+cti_add_route_(srp_server_t *UNUSED server, void *context, cti_reply_t callback, run_context_t client_queue,
+               struct in6_addr *prefix, int prefix_length, int priority, int domain_id,
+               bool stable, bool nat64, const char *file, int line)
 {
-    cti_tunnel_reply_t callback;
-    INFO("conn_ref = %p name = %s", conn_ref,
-         tunnel_name == NULL ? "<NULL>" : (char *)tunnel_name);
-    callback = conn_ref->callback.tunnel_reply;
-    if (callback != NULL) {
-        callback(conn_ref->context, tunnel_name, status);
-        conn_ref->callback.reply = NULL;
+    cti_callback_t app_callback;
+    app_callback.reply = callback;
+    cti_status_t errx;
+    object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    if (dict == NULL) {
+        ERROR("cti_do_prefix: no memory for command dictionary.");
+        return kCTIStatus_NoMemory;
     }
-    cti_connection_close(conn_ref);
+    xpc_dictionary_set_string(dict, "method", "RouteAdd");
+    xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
+    xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
+    xpc_dictionary_set_uint64(dict, "prefix_bits_len", prefix_length);
+    xpc_dictionary_set_data(dict, "prefix_bits", prefix, sizeof(*prefix));
+    xpc_dictionary_set_uint64(dict, "domain_id", domain_id);
+    xpc_dictionary_set_int64(dict, "priority", priority);
+    xpc_dictionary_set_bool(dict, "stable", stable);
+    xpc_dictionary_set_bool(dict, "nat64", nat64);
+
+    errx = setup_for_command(NULL, client_queue, "add_route", NULL, NULL, dict, "WpanctlCmd",
+                             context, app_callback, cti_internal_reply_callback, false, file, line);
+    xpc_release(dict);
+
+    return errx;
+}
+
+cti_status_t
+cti_remove_route_(srp_server_t *UNUSED server, void *NULLABLE context, cti_reply_t NONNULL callback,
+                  run_context_t NULLABLE client_queue, struct in6_addr *NONNULL prefix, int prefix_length,
+                  int domain_id, const char *file, int line)
+{
+    cti_callback_t app_callback;
+    app_callback.reply = callback;
+    cti_status_t errx;
+    object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    if (dict == NULL) {
+        ERROR("cti_do_prefix: no memory for command dictionary.");
+        return kCTIStatus_NoMemory;
+    }
+    xpc_dictionary_set_string(dict, "method", "RouteRemove");
+    xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
+    xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
+    xpc_dictionary_set_uint64(dict, "prefix_len_in_bits", prefix_length);
+    xpc_dictionary_set_data(dict, "prefix_bytes", prefix, sizeof(*prefix));
+    xpc_dictionary_set_uint64(dict, "domain_id", domain_id);
+
+    errx = setup_for_command(NULL, client_queue, "remove_route", NULL, NULL, dict, "WpanctlCmd",
+                             context, app_callback, cti_internal_reply_callback, false, file, line);
+    xpc_release(dict);
+
+    return errx;
+}
+
+static void
+cti_internal_string_event_reply(cti_connection_t NONNULL conn_ref, object_t reply, cti_status_t status_in)
+{
+    cti_string_property_reply_t callback = conn_ref->callback.string_property_reply;
+    xpc_retain(reply);
+    cti_status_t status = status_in;
+    const char *string_value = NULL;
+    if (status == kCTIStatus_NoError) {
+        // Initial reply (events do a get as well as subscribing to events, and the responses look different.
+        xpc_object_t command_result_value = xpc_dictionary_get_value(reply, "commandResult");
+        if (command_result_value != NULL) {
+            uint64_t command_result = xpc_int64_get_value(command_result_value);
+            if (command_result != 0) {
+                ERROR("[CX%d] nonzero result %llu", conn_ref->serial, command_result);
+                status = kCTIStatus_UnknownError;
+            } else {
+                object_t result_dictionary = xpc_dictionary_get_dictionary(reply, "commandData");
+                if (status == kCTIStatus_NoError) {
+                    if (result_dictionary != NULL) {
+                        const char *property_name = xpc_dictionary_get_string(result_dictionary, "property_name");
+                        if (property_name == NULL || strcmp(property_name, conn_ref->return_property_name)) {
+                            status = kCTIStatus_UnknownError;
+                        } else {
+                            string_value = xpc_dictionary_get_string(result_dictionary, "value");
+                            if (string_value == NULL) {
+                                status = kCTIStatus_UnknownError;
+                            }
+                        }
+                    } else {
+                        status = kCTIStatus_UnknownError;
+                    }
+                }
+            }
+        } else {
+            xpc_object_t event_data_dict = xpc_dictionary_get_dictionary(reply, "eventData");
+            if (event_data_dict == NULL) {
+                ERROR("[CX%d] no eventData dictionary", conn_ref->serial);
+                status = kCTIStatus_UnknownError;
+            } else {
+                // Event data looks like { "v_type" : 13, "path": <string>, "value": <string>, "key": <property_name> }
+                xpc_object_t value_array = xpc_dictionary_get_array(event_data_dict, "value");
+                if (value_array == NULL) {
+                    ERROR("[CX%d] eventData dictionary contains no 'value' key", conn_ref->serial);
+                    status = kCTIStatus_UnknownError;
+                } else {
+                    size_t count = xpc_array_get_count(value_array);
+                    if (count < 1) {
+                        ERROR("[CX%d] eventData value array has no elements", conn_ref->serial);
+                        status = kCTIStatus_UnknownError;
+                    } else {
+                        if (count > 1) {
+                            ERROR("[CX%d] eventData value array has %zd elements", conn_ref->serial, count);
+                            // This is weird, but we're not going to deliberately fail because of it.
+                        }
+                        string_value = xpc_array_get_string(value_array, 0);
+                        if (string_value == NULL) {
+                            ERROR("[CX%d] eventData value array's first element is not a string", conn_ref->serial);
+                            status = kCTIStatus_UnknownError;
+                        }
+                    }
+                }
+            }
+        }
+    }
+    if (callback != NULL) {
+        callback(conn_ref->context, string_value, status);
+    }
+    xpc_release(reply);
+}
+
+static void
+cti_internal_string_property_reply(cti_connection_t NONNULL conn_ref, object_t reply, cti_status_t status_in)
+{
+    cti_internal_string_event_reply(conn_ref, reply, status_in);
+    conn_ref->callback.reply = NULL;
+    if (conn_ref->connection != NULL) {
+        INFO("[CX%d] canceling connection %p", conn_ref->serial, conn_ref->connection);
+        xpc_connection_cancel(conn_ref->connection);
+    }
+    RELEASE_HERE(conn_ref, cti_connection);
 }
 
 cti_status_t
@@ -316,40 +931,151 @@
 {
     cti_callback_t app_callback;
     app_callback.string_property_reply = callback;
-    int ret;
-    cti_connection_t conn_ref;
-    ret = cti_connection_create(context, app_callback, cti_internal_string_property_reply, &conn_ref);
-    if (ret == kCTIStatus_NoError) {
-        if (cti_connection_message_create(conn_ref, kCTIMessageType_GetTunnelName, 0))
-        {
-            if (!cti_connection_message_send(conn_ref)) {
-                ret = kCTIStatus_Disconnected;
-            }
-        } else {
-            ret = kCTIStatus_NoMemory;
-        }
-        if (ret != kCTIStatus_NoError) {
-            cti_connection_close(conn_ref);
-        }
-    }
-    return ret;
+    cti_status_t errx;
+    object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
+    xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
+    xpc_dictionary_set_string(dict, "method", "PropGet");
+    xpc_dictionary_set_string(dict, "property_name", "Config:TUN:InterfaceName");
+
+    errx = setup_for_command(NULL, client_queue, "get_tunnel_name", NULL, "Config:TUN:InterfaceName", dict, "WpanctlCmd",
+                             context, app_callback, cti_internal_string_property_reply, false, file, line);
+    xpc_release(dict);
+
+    return errx;
 }
 
-
-// For event comamnds, we return failure and close; on success, we just wait for events to flow and return those.
-static void
-cti_internal_state_event_callback(cti_connection_t conn_ref, void *UNUSED object, cti_status_t status)
+cti_status_t
+cti_track_active_data_set_(srp_server_t *UNUSED server, cti_connection_t *ref, void *NULLABLE context,
+                           cti_reply_t NONNULL callback,
+                           run_context_t NULLABLE client_queue, const char *file, int line)
 {
-    cti_state_reply_t callback;
-    INFO("conn_ref = %p", conn_ref);
-    if (status != kCTIStatus_NoError) {
-        callback = conn_ref->callback.state_reply;
-        if (callback != NULL) {
-            callback(conn_ref->context, 0, status);
-            // Only one error callback ever.
-            conn_ref->callback.reply = NULL;
+    cti_callback_t app_callback;
+    app_callback.reply = callback;
+    cti_status_t errx;
+    object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
+    xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
+
+    errx = setup_for_command(ref, client_queue, "track_active_data_set", "ActiveDataSetChanged", NULL, dict, "WpanctlCmd",
+                             context, app_callback, cti_internal_event_reply_callback, true, file, line);
+    xpc_release(dict);
+
+    return errx;
+}
+
+cti_status_t
+cti_get_mesh_local_prefix_(srp_server_t *UNUSED server, void *NULLABLE context,
+                           cti_string_property_reply_t NONNULL callback,
+                           run_context_t NULLABLE client_queue, const char *file, int line)
+{
+    cti_callback_t app_callback;
+    app_callback.string_property_reply = callback;
+    cti_status_t errx;
+    object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
+    xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
+    xpc_dictionary_set_string(dict, "method", "PropGet");
+    xpc_dictionary_set_string(dict, "property_name", "IPv6:MeshLocalPrefix");
+
+    errx = setup_for_command(NULL, client_queue, "get_mesh_local_prefix", NULL,
+                             "IPv6:MeshLocalPrefix", dict, "WpanctlCmd",
+                             context, app_callback, cti_internal_string_property_reply, false, file, line);
+    xpc_release(dict);
+
+    return errx;
+}
+
+cti_status_t
+cti_get_mesh_local_address_(srp_server_t *UNUSED server, void *NULLABLE context,
+                            cti_string_property_reply_t NONNULL callback, run_context_t NULLABLE client_queue,
+                            const char *file, int line)
+{
+    cti_callback_t app_callback;
+    app_callback.string_property_reply = callback;
+    cti_status_t errx;
+    object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
+    xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
+    xpc_dictionary_set_string(dict, "method", "PropGet");
+    xpc_dictionary_set_string(dict, "property_name", "IPv6:MeshLocalAddress");
+
+    errx = setup_for_command(NULL, client_queue, "get_mesh_local_address", NULL,
+                             "IPv6:MeshLocalAddress", dict, "WpanctlCmd", context, app_callback,
+                             cti_internal_string_property_reply, false, file, line);
+    xpc_release(dict);
+
+    return errx;
+}
+
+static cti_status_t
+cti_event_or_response_extract(object_t *reply, object_t *result_dictionary)
+{
+    object_t *result = xpc_dictionary_get_dictionary(reply, "commandData");
+    if (result == NULL) {
+        result = xpc_dictionary_get_dictionary(reply, "eventData");
+    } else {
+        int command_status = (int)xpc_dictionary_get_int64(reply, "commandResult");
+        if (command_status != 0) {
+            INFO("nonzero status %d", command_status);
+            return kCTIStatus_UnknownError;
         }
-        cti_connection_close(conn_ref);
+    }
+    if (result != NULL) {
+        *result_dictionary = result;
+        return kCTIStatus_NoError;
+    }
+    INFO("null result");
+    return kCTIStatus_UnknownError;
+}
+
+static void
+cti_internal_state_reply_callback(cti_connection_t NONNULL conn_ref, object_t reply, cti_status_t status_in)
+{
+    cti_state_reply_t callback = conn_ref->callback.state_reply;
+    cti_network_state_t state = kCTI_NCPState_Unknown;
+    cti_status_t status = status_in;
+    if (status == kCTIStatus_NoError) {
+        object_t result_dictionary = NULL;
+        status = cti_event_or_response_extract(reply, &result_dictionary);
+        if (status == kCTIStatus_NoError) {
+            const char *state_name = xpc_dictionary_get_string(result_dictionary, "value");
+            if (state_name == NULL) {
+                status = kCTIStatus_UnknownError;
+            } else if (!strcmp(state_name, "uninitialized")) {
+                state = kCTI_NCPState_Uninitialized;
+            } else if (!strcmp(state_name, "uninitialized:fault")) {
+                state = kCTI_NCPState_Fault;
+            } else if (!strcmp(state_name, "uninitialized:upgrading")) {
+                state = kCTI_NCPState_Upgrading;
+            } else if (!strcmp(state_name, "offline:deep-sleep")) {
+                state = kCTI_NCPState_DeepSleep;
+            } else if (!strcmp(state_name, "offline")) {
+                state = kCTI_NCPState_Offline;
+            } else if (!strcmp(state_name, "offline:commissioned")) {
+                state = kCTI_NCPState_Commissioned;
+            } else if (!strcmp(state_name, "associating")) {
+                state = kCTI_NCPState_Associating;
+            } else if (!strcmp(state_name, "associating:credentials-needed")) {
+                state = kCTI_NCPState_CredentialsNeeded;
+            } else if (!strcmp(state_name, "associated")) {
+                state = kCTI_NCPState_Associated;
+            } else if (!strcmp(state_name, "associated:no-parent")) {
+                state = kCTI_NCPState_Isolated;
+            } else if (!strcmp(state_name, "associated:netwake-asleep")) {
+                state = kCTI_NCPState_NetWake_Asleep;
+            } else if (!strcmp(state_name, "associated:netwake-waking")) {
+                state = kCTI_NCPState_NetWake_Waking;
+            }
+        }
+    }
+    if (callback != NULL) {
+        INFO("[CX%d] calling callback for %p", conn_ref->serial, conn_ref);
+        callback(conn_ref->context, state, status);
     }
 }
 
@@ -359,39 +1085,69 @@
 {
     cti_callback_t app_callback;
     app_callback.state_reply = callback;
-    int ret;
-    cti_connection_t conn_ref;
-    ret = cti_connection_create(context, app_callback, cti_internal_state_event_callback, &conn_ref);
-    if (ret == kCTIStatus_NoError) {
-        if (cti_connection_message_create(conn_ref, kCTIMessageType_RequestStateEvents, 4)) {
-            if (!cti_connection_message_send(conn_ref)) {
-                ret = kCTIStatus_Disconnected;
-            }
-        } else {
-            ret = kCTIStatus_NoMemory;
-        }
-        if (ret != kCTIStatus_NoError) {
-            cti_connection_close(conn_ref);
-        }
-    }
-    return ret;
+    cti_status_t errx;
+    object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
+    xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
+    xpc_dictionary_set_string(dict, "method", "PropGet");
+    xpc_dictionary_set_string(dict, "property_name", "NCP:State");
+
+    errx = setup_for_command(ref, client_queue, "get_state", "NCP:State", NULL, dict, "WpanctlCmd",
+                             context, app_callback, cti_internal_state_reply_callback, false, file, line);
+    xpc_release(dict);
+
+    return errx;
 }
 
-typedef uint32_t cti_property_name_t;
+typedef const char *cti_property_name_t;
 
-// For event commands, we return failure and close; on success, we just wait for events to flow and return those.
 static void
-cti_internal_uint64_property_callback(cti_connection_t conn_ref, void *UNUSED object, cti_status_t status)
+cti_internal_uint64_property_callback(cti_connection_t NONNULL conn_ref, object_t reply, cti_status_t status_in)
 {
     cti_uint64_property_reply_t callback = conn_ref->callback.uint64_property_reply;
-    INFO("conn_ref = %p", conn_ref);
-    if (status != kCTIStatus_NoError) {
-        if (callback != NULL) {
-            callback(conn_ref->context, 0, status);
-            // Only one error callback ever.
-            conn_ref->callback.reply = NULL;
+    uint64_t uint64_val = 0;
+    cti_status_t status = status_in;
+    if (status == kCTIStatus_NoError) {
+        object_t result_dictionary = NULL;
+        status = cti_event_or_response_extract(reply, &result_dictionary);
+        if (status == kCTIStatus_NoError) {
+            object_t value = xpc_dictionary_get_value(result_dictionary, "value");
+            if (value == NULL) {
+                ERROR("[CX%d] no property value.", conn_ref->serial);
+            } else {
+                if (xpc_get_type(value) == XPC_TYPE_UINT64) {
+                    uint64_val = xpc_dictionary_get_uint64(result_dictionary, "value");
+                } else if (xpc_get_type(value) == XPC_TYPE_ARRAY) {
+                    size_t count = xpc_array_get_count(value);
+                    if (count != sizeof(uint64_val)) {
+                        goto fail;
+                    }
+                    for (size_t i = 0; i < sizeof(uint64_val); i++) {
+                        object_t element = xpc_array_get_value(value, i);
+                        if (xpc_get_type(element) != XPC_TYPE_UINT64) {
+                            goto fail;
+                        }
+                        uint64_t ev = xpc_array_get_uint64(value, i);
+                        if (ev > 255) {
+                            goto fail;
+                        }
+                        uint64_val = (uint64_val << 8) | ev;
+                    }
+                } else {
+                    char *value_string;
+                fail:
+                    value_string = xpc_copy_description(value);
+                    ERROR("[CX%d] property value is " PUB_S_SRP " instead of uint64_t or array of uint64_t byte values.",
+                          conn_ref->serial, value_string);
+                    free(value_string);
+                    status = kCTIStatus_Invalid;
+                }
+            }
         }
-        cti_connection_close(conn_ref);
+    }
+    if (callback != NULL) {
+        callback(conn_ref->context, uint64_val, status);
     }
 }
 
@@ -401,22 +1157,19 @@
 {
     cti_callback_t app_callback;
     app_callback.uint64_property_reply = callback;
-    int ret;
-    cti_connection_t conn_ref;
-    ret = cti_connection_create(context, app_callback, cti_internal_uint64_property_callback, &conn_ref);
-    if (ret == kCTIStatus_NoError) {
-        if (cti_connection_message_create(conn_ref, kCTIMessageType_RequestUInt64PropEvents, 4)) {
-            if (!cti_connection_u32_put(conn_ref, property_name) || !cti_connection_message_send(conn_ref)) {
-                ret = kCTIStatus_Disconnected;
-            }
-        } else {
-            ret = kCTIStatus_NoMemory;
-        }
-        if (ret != kCTIStatus_NoError) {
-            cti_connection_close(conn_ref);
-        }
-    }
-    return ret;
+    cti_status_t errx;
+    object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
+    xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
+    xpc_dictionary_set_string(dict, "method", "PropGet");
+    xpc_dictionary_set_string(dict, "property_name", property_name);
+
+    errx = setup_for_command(ref, client_queue, "get_uint64_property", property_name, NULL, dict, "WpanctlCmd",
+                             context, app_callback, cti_internal_uint64_property_callback, false, file, line);
+    xpc_release(dict);
+
+    return errx;
 }
 
 cti_status_t
@@ -435,20 +1188,50 @@
     return cti_get_uint64_property(ref, context, callback, client_queue, kCTIPropertyExtendedPANID, file, line);
 }
 
-// For event commands, we return failure and close; on success, we just wait for events to flow and return those.
 static void
-cti_internal_node_type_event_callback(cti_connection_t conn_ref, void *UNUSED object, cti_status_t status)
+cti_internal_network_node_type_callback(cti_connection_t NONNULL conn_ref, object_t reply, cti_status_t status_in)
 {
-    cti_network_node_type_reply_t callback;
-    INFO("conn_ref = %p", conn_ref);
-    if (status != kCTIStatus_NoError) {
-        callback = conn_ref->callback.network_node_type_reply;
-        if (callback != NULL) {
-            callback(conn_ref->context, 0, status);
-            // Only one error callback ever.
-            conn_ref->callback.reply = NULL;
+    cti_network_node_type_reply_t callback = conn_ref->callback.network_node_type_reply;
+    cti_network_node_type_t network_node_type = kCTI_NetworkNodeType_Unknown;
+    cti_status_t status = status_in;
+    if (status == kCTIStatus_NoError) {
+        object_t result_dictionary = NULL;
+        status = cti_event_or_response_extract(reply, &result_dictionary);
+        if (status == kCTIStatus_NoError) {
+            object_t value = xpc_dictionary_get_value(result_dictionary, "value");
+            if (value == NULL) {
+                ERROR("[CX%d] No node type returned.", conn_ref->serial);
+            } else if (xpc_get_type(value) != XPC_TYPE_STRING) {
+                char *value_string = xpc_copy_description(value);
+                ERROR("[CX%d] node type type is " PUB_S_SRP " instead of string.", conn_ref->serial, value_string);
+                free(value_string);
+            } else {
+                const char *node_type_name = xpc_dictionary_get_string(result_dictionary, "value");
+                if (!strcmp(node_type_name, "unknown")) {
+                    network_node_type = kCTI_NetworkNodeType_Unknown;
+                } else if (!strcmp(node_type_name, "router")) {
+                        network_node_type = kCTI_NetworkNodeType_Router;
+                } else if (!strcmp(node_type_name, "end-device")) {
+                    network_node_type = kCTI_NetworkNodeType_EndDevice;
+                } else if (!strcmp(node_type_name, "sleepy-end-device")) {
+                    network_node_type = kCTI_NetworkNodeType_SleepyEndDevice;
+                } else if (!strcmp(node_type_name, "synchronized-sleepy-end-device")) {
+                    network_node_type = kCTI_NetworkNodeType_SynchronizedSleepyEndDevice;
+                } else if (!strcmp(node_type_name, "nl-lurker")) {
+                    network_node_type = kCTI_NetworkNodeType_NestLurker;
+                } else if (!strcmp(node_type_name, "commissioner")) {
+                    network_node_type = kCTI_NetworkNodeType_Commissioner;
+                } else if (!strcmp(node_type_name, "leader")) {
+                    network_node_type = kCTI_NetworkNodeType_Leader;
+                } else if (!strcmp(node_type_name, "sleepy-router")) {
+                    network_node_type = kCTI_NetworkNodeType_SleepyRouter;
+                }
+            }
         }
-        cti_connection_close(conn_ref);
+    }
+    if (callback != NULL) {
+        INFO("[CX%d] calling callback for %p", conn_ref->serial, conn_ref);
+        callback(conn_ref->context, network_node_type, status);
     }
 }
 
@@ -459,22 +1242,19 @@
 {
     cti_callback_t app_callback;
     app_callback.network_node_type_reply = callback;
-    int ret;
-    cti_connection_t conn_ref;
-    ret = cti_connection_create(context, app_callback, cti_internal_node_type_event_callback, &conn_ref);
-    if (ret == kCTIStatus_NoError) {
-        if (cti_connection_message_create(conn_ref, kCTIMessageType_RequestRoleEvents, 4)) {
-            if (!cti_connection_message_send(conn_ref)) {
-                ret = kCTIStatus_Disconnected;
-            }
-        } else {
-            ret = kCTIStatus_NoMemory;
-        }
-        if (ret != kCTIStatus_NoError) {
-            cti_connection_close(conn_ref);
-        }
-    }
-    return ret;
+    cti_status_t errx;
+    object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
+    xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
+    xpc_dictionary_set_string(dict, "method", "PropGet");
+    xpc_dictionary_set_string(dict, "property_name", "Network:NodeType");
+
+    errx = setup_for_command(ref, client_queue, "get_network_node_type", "Network:NodeType", NULL, dict, "WpanctlCmd",
+                             context, app_callback, cti_internal_network_node_type_callback, false, file, line);
+    xpc_release(dict);
+
+    return errx;
 }
 
 static void
@@ -557,20 +1337,235 @@
     RELEASE(service, cti_service);
 }
 
-// For event comamnds, we return failure and close; on success, we just wait for events to flow and return those.
-static void
-cti_internal_service_event_callback(cti_connection_t conn_ref, void *UNUSED object, cti_status_t status)
+static uint8_t *
+cti_array_to_bytes(object_t array, size_t *length_ret, const char *log_name)
 {
-    cti_service_reply_t callback;
-    INFO("conn_ref = %p", conn_ref);
-    if (status != kCTIStatus_NoError) {
-        callback = conn_ref->callback.service_reply;
-        if (callback != NULL) {
-            callback(conn_ref->context, 0, status);
-            // Only one error callback ever.
-            conn_ref->callback.reply = NULL;
+    size_t length = xpc_array_get_count(array);
+    size_t i;
+    uint8_t *ret;
+
+    ret = malloc(length);
+    if (ret == NULL) {
+        ERROR(PUB_S_SRP ": no memory for return buffer", log_name);
+        return NULL;
+    }
+
+    for (i = 0; i < length; i++) {
+        uint64_t v = xpc_array_get_uint64(array, i);
+        ret[i] = v;
+    }
+    *length_ret = length;
+    return ret;
+}
+
+static cti_status_t
+cti_parse_services_array(cti_service_vec_t **services, object_t services_array)
+{
+    size_t services_array_length = xpc_array_get_count(services_array);
+    size_t i, j;
+    cti_service_vec_t *service_vec;
+    cti_service_t *service;
+    cti_status_t status = kCTIStatus_NoError;
+
+    service_vec = cti_service_vec_create(services_array_length);
+    if (service_vec == NULL) {
+        return kCTIStatus_NoMemory;
+    }
+
+    // Array of arrays
+    for (i = 0; i < services_array_length; i++) {
+        object_t service_array = xpc_array_get_value(services_array, i);
+        int match_count = 0;
+        bool matched_enterprisenum = false;
+        bool matched_origin = false;
+        bool matched_rloc16 = false;
+        bool matched_serverdata = false;
+        bool matched_servicedata = false;
+        bool matched_stable = false;
+        bool matched_service_id = false;
+        uint64_t enterprise_number = 0;
+        uint16_t rloc16 = 0;
+        uint8_t *server_data = NULL;
+        size_t server_data_length = 0;
+        uint8_t *service_data = NULL;
+        size_t service_data_length = 0;
+        int flags = 0;
+        uint16_t service_id = 0;
+
+        if (service_array == NULL) {
+            ERROR("Unable to get service array %zd", i);
+        } else {
+            size_t service_array_length = xpc_array_get_count(service_array);
+            for (j = 0; j < service_array_length; j++) {
+                object_t *array_sub_dict = xpc_array_get_value(service_array, j);
+                if (array_sub_dict == NULL) {
+                    ERROR("can't get service_array %zd subdictionary %zd", i, j);
+                    goto service_array_element_failed;
+                } else {
+                    const char *key = xpc_dictionary_get_string(array_sub_dict, "key");
+                    if (key == NULL) {
+                        ERROR("Invalid services array %zd subdictionary %zd: no key", i, j);
+                        goto service_array_element_failed;
+                    } else if (!strcmp(key, "EnterpriseNumber")) {
+                        if (matched_enterprisenum) {
+                            ERROR("services array %zd: Enterprise number appears twice.", i);
+                            goto service_array_element_failed;
+                        }
+                        enterprise_number = xpc_dictionary_get_uint64(array_sub_dict, "value");
+                        matched_enterprisenum = true;
+                    } else if (!strcmp(key, "Origin")) {
+                        if (matched_origin) {
+                            ERROR("Services array %zd: Origin appears twice.", i);
+                            goto service_array_element_failed;
+                        }
+                        const char *origin_string = xpc_dictionary_get_string(array_sub_dict, "value");
+                        if (origin_string == NULL) {
+                            ERROR("Unable to get origin string from services array %zd", i);
+                            goto service_array_element_failed;
+                        } else if (!strcmp(origin_string, "user")) {
+                            // Not NCP
+                        } else if (!strcmp(origin_string, "ncp")) {
+                            flags |= kCTIFlag_NCP;
+                        } else {
+                            ERROR("unknown origin " PUB_S_SRP, origin_string);
+                            goto service_array_element_failed;
+                        }
+                        matched_origin = true;
+                    } else if (!strcmp(key, "ServerData")) {
+                        if (matched_serverdata) {
+                            ERROR("Services array %zd: Server data appears twice.", i);
+                            goto service_array_element_failed;
+                        }
+                        server_data = cti_array_to_bytes(xpc_dictionary_get_array(array_sub_dict, "value"),
+                                                         &server_data_length, "Server data");
+                        if (server_data == NULL) {
+                            goto service_array_element_failed;
+                        }
+                        matched_serverdata = true;
+                    } else if (!strcmp(key, "ServiceData")) {
+                        if (matched_servicedata) {
+                            ERROR("Services array %zd: Service data appears twice.", i);
+                            goto service_array_element_failed;
+                        }
+                        service_data = cti_array_to_bytes(xpc_dictionary_get_array(array_sub_dict, "value"),
+                                                          &service_data_length, "Service data");
+                        if (service_data == NULL) {
+                            goto service_array_element_failed;
+                        }
+                        matched_servicedata = true;
+                    } else if (!strcmp(key, "Stable")) {
+                        if (matched_stable) {
+                            ERROR("Services array %zd: Stable state appears twice.", i);
+                            goto service_array_element_failed;
+                        }
+                        if (xpc_dictionary_get_bool(array_sub_dict, "value")) {
+                            flags |= kCTIFlag_Stable;
+                        }
+                        matched_stable = true;
+                    } else if (!strcmp(key, "RLOC16")) {
+                        if (matched_rloc16) {
+                            ERROR("Services array %zd: Stable state appears twice.", i);
+                            goto service_array_element_failed;
+                        }
+                        rloc16 = (uint16_t)xpc_dictionary_get_uint64(array_sub_dict, "value");
+                        matched_rloc16 = true;
+                    } else if (!strcmp(key, "ServiceId")) {
+                        if (matched_service_id) {
+                            ERROR("Services array %zd: Stable state appears twice.", i);
+                            goto service_array_element_failed;
+                        }
+                        service_id = (uint16_t)xpc_dictionary_get_int64(array_sub_dict, "value");
+                        matched_service_id = true;
+                    } else {
+                        INFO("Unknown key in service array %zd subdictionary %zd: " PUB_S_SRP, i, j, key);
+                        // Not a failure, but don't count it.
+                        continue;
+                    }
+                    match_count++;
+                }
+            }
+            if (match_count != 7) {
+                ERROR("expecting %d sub-dictionaries to service array %zd, but got %d.",
+                      7, i, match_count);
+                // No service ID is not fatal (yet), but if some other data is missing, that /is/ fatal.
+                if (match_count < 6 || (match_count == 6 && matched_service_id)) {
+                    goto service_array_element_failed;
+                }
+            }
+            uint16_t service_type, service_version;
+            if (enterprise_number == THREAD_ENTERPRISE_NUMBER) {
+                // two-byte service data for anycast service while on-byte for unicast
+                // and pref-id
+                if (service_data_length != 1 && service_data_length != 2) {
+                    INFO("Invalid service data: length = %zd", service_data_length);
+                    goto service_array_element_failed;
+                }
+                service_type = service_data[0];
+                service_version = 1;
+            } else {
+                // We don't support any other enterprise numbers.
+                service_type = service_version = 0;
+            }
+
+            service = cti_service_create(enterprise_number, rloc16, service_type, service_version,
+                                         service_data, service_data_length, server_data,
+                                         server_data_length, service_id, flags);
+            if (service == NULL) {
+                ERROR("Unable to store service %lld %d %d: out of memory.", enterprise_number,
+                      service_type, service_version);
+            } else {
+                service_data = NULL;
+                server_data = NULL;
+                service_vec->services[i] = service;
+            }
+            goto done_with_service_array;
+        service_array_element_failed:
+            if (status == kCTIStatus_NoError) {
+                status = kCTIStatus_UnknownError;
+            }
+        done_with_service_array:
+            if (server_data != NULL) {
+                free(server_data);
+            }
+            if (service_data != NULL) {
+                free(service_data);
+            }
         }
-        cti_connection_close(conn_ref);
+    }
+    if (status == kCTIStatus_NoError) {
+        *services = service_vec;
+    } else {
+        if (service_vec != NULL) {
+            RELEASE_HERE(service_vec, cti_service_vec);
+        }
+    }
+    return status;
+}
+
+static void
+cti_internal_service_reply_callback(cti_connection_t NONNULL conn_ref, object_t reply, cti_status_t status_in)
+{
+    cti_service_reply_t callback = conn_ref->callback.service_reply;
+    cti_service_vec_t *vec = NULL;
+    cti_status_t status = status_in;
+    if (status == kCTIStatus_NoError) {
+        object_t result_dictionary = NULL;
+        status = cti_event_or_response_extract(reply, &result_dictionary);
+        if (status == kCTIStatus_NoError) {
+            object_t *value = xpc_dictionary_get_array(result_dictionary, "value");
+            if (value == NULL) {
+                INFO("[CX%d] services array not present in Thread:Services event.", conn_ref->serial);
+            } else {
+                status = cti_parse_services_array(&vec, value);
+            }
+        }
+    }
+    if (callback != NULL) {
+        INFO("[CX%d] calling callback for %p", conn_ref->serial, conn_ref);
+        callback(conn_ref->context, vec, status);
+    }
+    if (vec != NULL) {
+        RELEASE_HERE(vec, cti_service_vec);
     }
 }
 
@@ -581,22 +1576,19 @@
 {
     cti_callback_t app_callback;
     app_callback.service_reply = callback;
-    int ret;
-    cti_connection_t conn_ref;
-    ret = cti_connection_create(context, app_callback, cti_internal_service_event_callback, &conn_ref);
-    if (ret == kCTIStatus_NoError) {
-        if (cti_connection_message_create(conn_ref, kCTIMessageType_RequestServiceEvents, 4)) {
-            if (!cti_connection_message_send(conn_ref)) {
-                ret = kCTIStatus_Disconnected;
-            }
-        } else {
-            ret = kCTIStatus_NoMemory;
-        }
-        if (ret != kCTIStatus_NoError) {
-            cti_connection_close(conn_ref);
-        }
-    }
-    return ret;
+    cti_status_t errx;
+    object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
+    xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
+    xpc_dictionary_set_string(dict, "method", "PropGet");
+    xpc_dictionary_set_string(dict, "property_name", "Thread:Services");
+
+    errx = setup_for_command(ref, client_queue, "get_service_list", "Thread:Services", NULL, dict, "WpanctlCmd",
+                             context, app_callback, cti_internal_service_reply_callback, false, file, line);
+    xpc_release(dict);
+
+    return errx;
 }
 
 static void
@@ -669,20 +1661,144 @@
     RELEASE(prefix, cti_prefix);
 }
 
-// For event comamnds, we return failure and close; on success, we just wait for events to flow and return those.
-static void
-cti_internal_prefix_event_callback(cti_connection_t conn_ref, void *UNUSED object, cti_status_t status)
+static cti_status_t
+cti_parse_prefixes_array(cti_prefix_vec_t **vec_ret, object_t prefixes_array)
 {
-    cti_prefix_reply_t callback;
-    INFO("conn_ref = %p", conn_ref);
-    if (status != kCTIStatus_NoError) {
-        callback = conn_ref->callback.prefix_reply;
-        if (callback != NULL && conn_ref->context != NULL) {
-            callback(conn_ref->context, 0, status);
-            // Only one error callback ever.
-            conn_ref->callback.reply = NULL;
+    size_t prefixes_array_length = xpc_array_get_count(prefixes_array);
+    size_t i, j;
+    cti_prefix_vec_t *prefixes = cti_prefix_vec_create(prefixes_array_length);
+    cti_status_t status = kCTIStatus_NoError;
+
+    if (prefixes == NULL) {
+        INFO("no memory.");
+        status = kCTIStatus_NoMemory;
+    } else {
+        // Array of arrays
+        for (i = 0; i < prefixes_array_length; i++) {
+            object_t prefix_array = xpc_array_get_value(prefixes_array, i);
+            int match_count = 0;
+            bool matched_address = false;
+            bool matched_metric = false;
+            const char *destination = NULL;
+            int metric = 0;
+            struct in6_addr prefix_addr;
+
+            if (prefix_array == NULL) {
+                ERROR("Unable to get prefix array %zu", i);
+            } else {
+                size_t prefix_array_length = xpc_array_get_count(prefix_array);
+                for (j = 0; j < prefix_array_length; j++) {
+                    object_t *array_sub_dict = xpc_array_get_value(prefix_array, j);
+                    if (array_sub_dict == NULL) {
+                        ERROR("can't get prefix_array %zu subdictionary %zu", i, j);
+                        goto done_with_prefix_array;
+                    } else {
+                        const char *key = xpc_dictionary_get_string(array_sub_dict, "key");
+                        if (key == NULL) {
+                            ERROR("Invalid prefixes array %zu subdictionary %zu: no key", i, j);
+                            goto done_with_prefix_array;
+                        }
+                        // Fix me: when <rdar://problem/59371674> is fixed, remove Addreess key test.
+                        else if (!strcmp(key, "Addreess") || !strcmp(key, "Address")) {
+                            if (matched_address) {
+                                ERROR("prefixes array %zu: Address appears twice.", i);
+                                goto done_with_prefix_array;
+                            }
+                            destination = xpc_dictionary_get_string(array_sub_dict, "value");
+                            if (destination == NULL) {
+                                INFO("null address");
+                                goto done_with_prefix_array;
+                            }
+                            matched_address = true;
+                        } else if (!strcmp(key, "Metric")) {
+                            if (matched_metric) {
+                                ERROR("prefixes array %zu: Metric appears twice.", i);
+                                goto done_with_prefix_array;
+                            }
+                            metric = (int)xpc_dictionary_get_uint64(array_sub_dict, "value");
+                            matched_metric = true;
+                        } else {
+                            ERROR("Unknown key in prefix array %zu subdictionary %zu: " PUB_S_SRP, i, j, key);
+                            goto done_with_prefix_array;
+                        }
+                        match_count++;
+                    }
+                }
+                if (match_count != 2) {
+                    ERROR("expecting %d sub-dictionaries to prefix array %zu, but got %d.",
+                          2, i, match_count);
+                    goto done_with_prefix_array;
+                }
+
+                // The prefix is in IPv6 address presentation form, so convert it to bits.
+                char prefix_buffer[INET6_ADDRSTRLEN];
+                const char *slash = strchr(destination, '/');
+                size_t prefix_pres_len = slash - destination;
+                if (prefix_pres_len >= INET6_ADDRSTRLEN - 1) {
+                    ERROR("prefixes array %zu: destination is longer than maximum IPv6 address string: " PUB_S_SRP,
+                          j, destination);
+                    goto done_with_prefix_array;
+                }
+#ifndef __clang_analyzer__ // destination is never null at this point
+                memcpy(prefix_buffer, destination, prefix_pres_len);
+#endif
+                prefix_buffer[prefix_pres_len] = 0;
+                inet_pton(AF_INET6, prefix_buffer, &prefix_addr);
+
+                // Also convert the prefix.
+                char *endptr = NULL;
+                int prefix_len = (int)strtol(slash + 1, &endptr, 10);
+                if (endptr == slash + 1 || *endptr != 0 || prefix_len != 64) {
+                    INFO("bogus prefix length provided by thread: " PUB_S_SRP, destination);
+                    prefix_len = 64;
+                }
+
+                cti_prefix_t *prefix = cti_prefix_create(&prefix_addr, prefix_len, metric, 0, 0, false, false);
+                if (prefix != NULL) {
+                    prefixes->prefixes[i] = prefix;
+                }
+                continue;
+            done_with_prefix_array:
+                status = kCTIStatus_UnknownError;
+            }
         }
-        cti_connection_close(conn_ref);
+    }
+    if (status == kCTIStatus_NoError) {
+        *vec_ret = prefixes;
+    } else {
+        if (prefixes != NULL) {
+            RELEASE_HERE(prefixes, cti_prefix_vec);
+        }
+    }
+    return status;
+}
+
+static void
+cti_internal_prefix_reply_callback(cti_connection_t NONNULL conn_ref, object_t reply, cti_status_t status_in)
+{
+    cti_prefix_reply_t callback = conn_ref->callback.prefix_reply;
+    cti_status_t status = status_in;
+    cti_prefix_vec_t *vec = NULL;
+    object_t result_dictionary = NULL;
+    if (status == kCTIStatus_NoError) {
+        status = cti_event_or_response_extract(reply, &result_dictionary);
+        if (status == kCTIStatus_NoError) {
+            object_t *value = xpc_dictionary_get_array(result_dictionary, "value");
+            if (value == NULL) {
+                INFO("prefixes array not present in IPv6:Routes event.");
+            } else {
+                status = cti_parse_prefixes_array(&vec, value);
+            }
+        }
+    }
+    if (callback != NULL) {
+        INFO("[CX%d] calling callback for %p", conn_ref->serial, conn_ref);
+        callback(conn_ref->context, vec, status);
+    } else {
+        INFO("[CX%d] Not calling callback.", conn_ref->serial);
+    }
+    if (vec != NULL) {
+        RELEASE_HERE(vec, cti_prefix_vec);
     }
 }
 
@@ -693,230 +1809,645 @@
 {
     cti_callback_t app_callback;
     app_callback.prefix_reply = callback;
-    int ret;
-    cti_connection_t conn_ref;
-    ret = cti_connection_create(context, app_callback, cti_internal_prefix_event_callback, &conn_ref);
-    if (ret == kCTIStatus_NoError) {
-        if (cti_connection_message_create(conn_ref, kCTIMessageType_RequestPrefixEvents, 4)) {
-            if (!cti_connection_message_send(conn_ref)) {
-                ret = kCTIStatus_Disconnected;
-            }
-        } else {
-            ret = kCTIStatus_NoMemory;
-        }
-        if (ret != kCTIStatus_NoError) {
-            cti_connection_close(conn_ref);
-        }
-    }
-    return ret;
+    cti_status_t errx;
+    object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
+    xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
+    xpc_dictionary_set_string(dict, "method", "PropGet");
+    xpc_dictionary_set_string(dict, "property_name", "IPv6:Routes");
+
+    errx = setup_for_command(ref, client_queue, "get_prefix_list", "IPv6:Routes", NULL, dict, "WpanctlCmd",
+                             context, app_callback, cti_internal_prefix_reply_callback, false, file, line);
+    xpc_release(dict);
+
+    return errx;
 }
 
+static void
+cti_route_finalize(cti_route_t *route)
+{
+    free(route);
+}
+
+static void
+cti_route_vec_finalize(cti_route_vec_t *routes)
+{
+    size_t i;
+
+    if (routes->routes != NULL) {
+        for (i = 0; i < routes->num; i++) {
+            if (routes->routes[i] != NULL) {
+                RELEASE_HERE(routes->routes[i], cti_route);
+            }
+        }
+        free(routes->routes);
+    }
+    free(routes);
+}
+
+cti_route_vec_t *
+cti_route_vec_create_(size_t num_routes, const char *file, int line)
+{
+    cti_route_vec_t *routes = calloc(1, sizeof(*routes));
+    if (routes != NULL) {
+        if (num_routes != 0) {
+            routes->routes = calloc(num_routes, sizeof(cti_route_t *));
+            if (routes->routes == NULL) {
+                free(routes);
+                return NULL;
+            }
+        }
+        routes->num = num_routes;
+        RETAIN(routes, cti_route_vec);
+    }
+    return routes;
+}
+
+void
+cti_route_vec_release_(cti_route_vec_t *routes, const char *file, int line)
+{
+    RELEASE(routes, cti_route_vec);
+}
+
+cti_route_t *
+cti_route_create_(struct in6_addr *prefix, int prefix_length, offmesh_route_origin_t origin,
+                  bool nat64, bool stable, offmesh_route_preference_t preference, int rloc,
+                  bool next_hop_is_host, const char *file, int line)
+{
+    cti_route_t *route_ret = calloc(1, sizeof(*route_ret));
+    if (prefix != NULL) {
+        route_ret->prefix = *prefix;
+        route_ret->prefix_length = prefix_length;
+        route_ret->origin = origin;
+        route_ret->nat64 = nat64;
+        route_ret->stable = stable;
+        route_ret->preference = preference;
+        route_ret->rloc = rloc;
+        route_ret->next_hop_is_host = next_hop_is_host;
+        RETAIN(route_ret, cti_route);
+    }
+    return route_ret;
+}
+
+static cti_status_t
+cti_parse_offmesh_routes_array(cti_route_vec_t **vec_ret, object_t routes_array)
+{
+    size_t i, j, routes_array_length = xpc_array_get_count(routes_array);
+    cti_route_vec_t *routes = cti_route_vec_create(routes_array_length);
+    cti_status_t status = kCTIStatus_NoError;
+
+    if (routes == NULL) {
+        INFO("no memory.");
+        status = kCTIStatus_NoMemory;
+    } else {
+        // Array of arrays
+        for (i = 0; i < routes_array_length; i++) {
+            object_t route_array = xpc_array_get_value(routes_array, i);
+            if (route_array == NULL) {
+                ERROR("Unable to get route array %zu", i);
+            } else {
+                bool nat64 = false, stable = false, next_hop_is_host = false;
+                int rloc = 0, prefix_len = 0;
+                offmesh_route_preference_t pref = offmesh_route_preference_low;
+                offmesh_route_origin_t origin = offmesh_route_origin_user;
+                struct in6_addr prefix_addr = { };
+                size_t route_array_length = xpc_array_get_count(route_array);
+                for (j = 0; j < route_array_length; j++) {
+                    object_t *array_sub_dict = xpc_array_get_value(route_array, j);
+                    if (array_sub_dict == NULL) {
+                        ERROR("can't get routes_array %zu subdictionary %zu", i, j);
+                        goto done_with_route_array;
+                    }
+                    const char *key = xpc_dictionary_get_string(array_sub_dict, "key");
+                    if (key == NULL) {
+                        ERROR("Invalid routes array %zu subdictionary %zu: no key", i, j);
+                        goto done_with_route_array;
+                    } else if (!strcmp(key, "nat64")) {
+                        if (xpc_dictionary_get_uint64(array_sub_dict, "value") == 1) {
+                            nat64 = true;
+                        }
+                    } else if (!strcmp(key, "origin")) {
+                        const char *origin_str = xpc_dictionary_get_string(array_sub_dict, "value");
+                        if (origin_str == NULL) {
+                            ERROR("Invalid routes array %zu subdictionary %zu: NULL origin", i, j);
+                            goto done_with_route_array;
+                        }
+                        if (!strcmp(origin_str, "ncp")) {
+                            origin = offmesh_route_origin_ncp;
+                        }
+                    } else if (!strcmp(key, "stable")) {
+                        if (xpc_dictionary_get_uint64(array_sub_dict, "value") == 1) {
+                            stable = true;
+                        }
+                    } else if (!strcmp(key, "nextHopIsHost")) {
+                        if (xpc_dictionary_get_bool(array_sub_dict, "value") == true) {
+                            next_hop_is_host = true;
+                        }
+                    } else if (!strcmp(key, "rloc")) {
+                        rloc = (int)xpc_dictionary_get_uint64(array_sub_dict, "value");
+                    } else if (!strcmp(key, "preference")) {
+                        const char *pref_str = xpc_dictionary_get_string(array_sub_dict, "value");
+                        if (pref_str == NULL) {
+                            ERROR("Invalid routes array %zu subdictionary %zu: NULL preference", i, j);
+                            goto done_with_route_array;
+                        }
+                        if (!strcmp(pref_str, "high")) {
+                            pref = offmesh_route_preference_high;
+                        } else if (!strcmp(pref_str, "medium")) {
+                            pref = offmesh_route_preference_medium;
+                        }
+                    } else if (!strcmp(key, "address")) {
+                        const char *addr_str = xpc_dictionary_get_string(array_sub_dict, "value");
+                        if (addr_str == NULL) {
+                            ERROR("Invalid routes array %zu subdictionary %zu: NULL address", i, j);
+                            goto done_with_route_array;
+                        }
+                        // The prefix is in IPv6 address presentation form, so convert it to bits.
+                        char prefix_buffer[INET6_ADDRSTRLEN];
+                        const char *slash = strchr(addr_str, '/');
+                        if (slash == NULL) {
+                            ERROR("bad address " PRI_S_SRP, addr_str);
+                            goto done_with_route_array;
+                        }
+                        size_t prefix_pres_len = slash - addr_str;
+                        if (prefix_pres_len > INET6_ADDRSTRLEN - 1) {
+                            ERROR("routes array %zu: destination is longer than maximum IPv6 address string: " PRI_S_SRP,
+                                 i, addr_str);
+                                goto done_with_route_array;
+                        }
+#ifndef __clang_analyzer__ // destination is never null at this point
+                        memcpy(prefix_buffer, addr_str, prefix_pres_len);
+#endif
+                        prefix_buffer[prefix_pres_len] = 0;
+                        if (inet_pton(AF_INET6, prefix_buffer, &prefix_addr) != 1) {
+                            ERROR("invalid ipv6 address " PRI_S_SRP, prefix_buffer);
+                            goto done_with_route_array;
+                        }
+                        // Convert the prefix.
+                        char *endptr = NULL;
+                        long tmp = strtol(slash + 1, &endptr, 10);
+                        if (endptr == slash + 1 || *endptr != 0 || tmp < 0 || tmp > 128) {
+                            ERROR("bogus ipv6 prefix length provided by thread: " PRI_S_SRP, addr_str);
+                            goto done_with_route_array;
+                        } else {
+                            prefix_len = (int)tmp;
+                        }
+                    } else {
+                        ERROR("Invalid routes array %zu subdictionary %zu: unknown key " PUB_S_SRP, i, j, key);
+                        goto done_with_route_array;
+                    }
+                }
+                cti_route_t *r = cti_route_create(&prefix_addr, prefix_len, origin, nat64, stable, pref, rloc, next_hop_is_host);
+                if (r == NULL) {
+                    ERROR("No memory when create route.");
+                    goto done_with_route_array;
+                } else {
+                    SEGMENTED_IPv6_ADDR_GEN_SRP(prefix_addr.s6_addr, prefix_buf);
+                    INFO("Got offmesh route " PRI_SEGMENTED_IPv6_ADDR_SRP " len %d origin:" PUB_S_SRP " nat64:"
+                         PUB_S_SRP " stable:" PUB_S_SRP " preference:%d rloc:0x%04x next_hop_is_host:" PUB_S_SRP,
+                         SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix_addr.s6_addr, prefix_buf), prefix_len,
+                         origin == offmesh_route_origin_user ? "user":"ncp", nat64 ? "yes" : "no",
+                         stable ? "yes" : "no", pref, rloc, next_hop_is_host ? "yes" : "no");
+                         routes->routes[i] = r;
+                }
+                continue;
+            done_with_route_array:
+                status = kCTIStatus_UnknownError;
+                break;
+            }
+        }
+    }
+    if (status == kCTIStatus_NoError) {
+        *vec_ret = routes;
+    } else {
+        if (routes != NULL) {
+            RELEASE_HERE(routes, cti_route_vec);
+        }
+    }
+    return status;
+}
+
+static void
+cti_internal_offmesh_route_reply_callback(cti_connection_t NONNULL conn_ref, object_t reply, cti_status_t status_in)
+{
+    cti_offmesh_route_reply_t callback = conn_ref->callback.offmesh_route_reply;
+    cti_status_t status = status_in;
+    cti_route_vec_t *vec = NULL;
+    object_t result_dictionary = NULL;
+    if (status == kCTIStatus_NoError) {
+        status = cti_event_or_response_extract(reply, &result_dictionary);
+        if (status == kCTIStatus_NoError) {
+            object_t *value = xpc_dictionary_get_array(result_dictionary, "value");
+            if (value == NULL) {
+                INFO("offmesh route array not present in Thread:OffMeshroutes event.");
+            } else {
+                status = cti_parse_offmesh_routes_array(&vec, value);
+            }
+        }
+    }
+    if (callback != NULL) {
+        INFO("[CX%d] calling callback for %p", conn_ref->serial, conn_ref);
+        callback(conn_ref->context, vec, status);
+    } else {
+        INFO("[CX%d] Not calling callback for %p", conn_ref->serial, conn_ref);
+    }
+    if (vec != NULL) {
+        RELEASE_HERE(vec, cti_route_vec);
+    }
+}
+
+cti_status_t
+cti_get_offmesh_route_list_(srp_server_t *UNUSED server, cti_connection_t *ref,
+                            void *NULLABLE context, cti_offmesh_route_reply_t NONNULL callback,
+                            run_context_t NULLABLE client_queue, const char *file, int line)
+{
+    cti_callback_t app_callback;
+    app_callback.offmesh_route_reply = callback;
+    cti_status_t errx;
+    object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
+    xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
+    xpc_dictionary_set_string(dict, "method", "PropGet");
+    xpc_dictionary_set_string(dict, "property_name", "Thread:OffMeshroutes");
+
+    errx = setup_for_command(ref, client_queue, "get_offmesh_route_list", "Thread:OffMeshroutes", NULL, dict, "WpanctlCmd",
+                             context, app_callback, cti_internal_offmesh_route_reply_callback, false, file, line);
+    INFO("get_offmesh_route_list result %d", errx);
+    xpc_release(dict);
+
+    return errx;
+}
+
+static cti_status_t
+cti_parse_onmesh_prefix_array(cti_prefix_vec_t **vec_ret, object_t prefix_array)
+{
+    size_t i, prefix_array_length = xpc_array_get_count(prefix_array);
+    cti_prefix_vec_t *prefixes = cti_prefix_vec_create(prefix_array_length);
+    cti_status_t status = kCTIStatus_NoError;
+
+    if (prefixes == NULL) {
+        INFO("no memory.");
+        status = kCTIStatus_NoMemory;
+    } else {
+        for (i = 0; i < prefix_array_length; i++) {
+            const char *route = xpc_array_get_string(prefix_array, i);
+            bool stable = false;
+            int rloc = 0, prefix_len = 0, flags = 0;
+            bool ncp = false;
+            struct in6_addr prefix_addr = { };
+
+            char *dup = strdup(route);
+            if (dup == NULL) {
+                INFO("no memory when strdup");
+                goto done_with_prefix_array;
+            }
+            char *token, *remain = dup;
+            int index = 0;
+            while ((token = strtok_r(remain, " ", &remain))) {
+                if (index == 0) {
+                    if (!inet_pton(AF_INET6, token, &prefix_addr)) {
+                        ERROR("invalid ipv6 address " PUB_S_SRP, token);
+                        goto done_with_prefix_array;
+                    }
+                } else if (index == 1) {
+                    if (strncmp(token, "prefix_len:", 11)) {
+                        ERROR("expecting prefix_len rather than " PRI_S_SRP " at %d", token, index);
+                        goto done_with_prefix_array;
+                    }
+                    char *endptr = NULL;
+                    prefix_len = (int)strtol(token + 11, &endptr, 10);
+                } else if (index == 2) {
+                    if (strncmp(token, "origin:", 7)) {
+                        ERROR("expecting origin rather than " PRI_S_SRP " at %d", token, index);
+                        goto done_with_prefix_array;
+                    }
+                    if (!strcmp(token + 7, "ncp")){
+                        ncp = true;
+                    } else if (strcmp(token + 7, "user")) {
+                        ERROR("unexpected origin: " PRI_S_SRP, token + 7);
+                        goto done_with_prefix_array;
+                    }
+                } else if (index == 3) {
+                    if (strncmp(token, "stable:", 7)) {
+                        ERROR("expecting table rather than " PRI_S_SRP " at %d", token, index);
+                        goto done_with_prefix_array;
+                    }
+                    if (!strcmp(token + 7, "yes")){
+                        stable = true;
+                    } else if (strcmp(token + 7, "no")) {
+                        ERROR("unexpected boolean state: " PRI_S_SRP, token + 7);
+                        goto done_with_prefix_array;
+                    }
+                } else if (index == 4) {
+                    if (strncmp(token, "flags:", 6)) {
+                        ERROR("expecting flags rather than " PRI_S_SRP " at %d", token, index);
+                        goto done_with_prefix_array;
+                    }
+                    char *endptr = NULL;
+                    flags = ntohs((int)strtol(token + 6, &endptr, 16));
+                } else if (index == 14) {
+                    if (strncmp(token, "rloc:", 5)) {
+                        ERROR("expecting rloc rather than " PRI_S_SRP " at %d", token, index);
+                        goto done_with_prefix_array;
+                    }
+                    char *endptr = NULL;
+                    rloc = (int)strtol(token + 5, &endptr, 16);
+                } else {
+                    // Anything else is in the parsed flags, which we don't care about, or is a bogon, which will probably result
+                    // in a failure elsewhere. We're not really expecting bogons, so putting in extra code here to flag them would
+                    // be useless.
+                }
+                index++;
+            }
+            SEGMENTED_IPv6_ADDR_GEN_SRP(prefix_addr.s6_addr, prefix_buf);
+            INFO("got prefix " PRI_SEGMENTED_IPv6_ADDR_SRP " len %d origin:" PUB_S_SRP " stable:" PUB_S_SRP " flags:%x rloc:%04x",
+                 SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix_addr.s6_addr, prefix_buf), prefix_len,
+                 ncp ? "ncp" : "user", stable ? "yes" : "no", flags, rloc);
+            cti_prefix_t *r = cti_prefix_create(&prefix_addr, prefix_len, 0, flags, rloc, stable, ncp);
+            if (r) {
+                prefixes->prefixes[i] = r;
+            } else {
+                ERROR("no memory for parsed prefix!");
+                goto done_with_prefix_array;
+            }
+            free(dup);
+            continue;
+        done_with_prefix_array:
+            status = kCTIStatus_UnknownError;
+            free(dup);
+            break;
+        }
+    }
+    if (status == kCTIStatus_NoError) {
+        *vec_ret = prefixes;
+    } else {
+        if (prefixes != NULL) {
+            RELEASE_HERE(prefixes, cti_prefix_vec);
+        }
+    }
+    return status;
+}
+
+static void
+cti_internal_onmesh_prefix_reply_callback(cti_connection_t NONNULL conn_ref, object_t reply, cti_status_t status_in)
+{
+    cti_onmesh_prefix_reply_t callback = conn_ref->callback.onmesh_prefix_reply;
+    cti_status_t status = status_in;
+    cti_prefix_vec_t *vec = NULL;
+    object_t result_dictionary = NULL;
+    if (status == kCTIStatus_NoError) {
+        status = cti_event_or_response_extract(reply, &result_dictionary);
+        if (status == kCTIStatus_NoError) {
+            object_t *value = xpc_dictionary_get_array(result_dictionary, "value");
+            if (value == NULL) {
+                INFO("onmesh prefix array not present in Thread:OnMeshPrefixes event.");
+            } else {
+                status = cti_parse_onmesh_prefix_array(&vec, value);
+            }
+        }
+    }
+    if (callback != NULL) {
+        INFO("[CX%d] calling callback for %p", conn_ref->serial, conn_ref);
+        callback(conn_ref->context, vec, status);
+    } else {
+        INFO("[CX%d] not calling callback for %p", conn_ref->serial, conn_ref);
+    }
+    if (vec != NULL) {
+        RELEASE_HERE(vec, cti_prefix_vec);
+    }
+}
+
+cti_status_t
+cti_get_onmesh_prefix_list_(srp_server_t *UNUSED server, cti_connection_t NULLABLE *NULLABLE ref,
+                            void *NULLABLE context, cti_onmesh_prefix_reply_t NONNULL callback,
+                            run_context_t NULLABLE client_queue, const char *file, int line)
+{
+    cti_callback_t app_callback;
+    app_callback.onmesh_prefix_reply = callback;
+    cti_status_t errx;
+    object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
+    xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
+    xpc_dictionary_set_string(dict, "method", "PropGet");
+    xpc_dictionary_set_string(dict, "property_name", "Thread:OnMeshPrefixes");
+
+    errx = setup_for_command(ref, client_queue, "get_onmesh_prefix_list", "Thread:OnMeshPrefixes", NULL, dict,
+                             "WpanctlCmd", context, app_callback, cti_internal_onmesh_prefix_reply_callback, false,
+                             file, line);
+    INFO("result %d", errx);
+    xpc_release(dict);
+
+    return errx;
+}
+
+static void
+cti_internal_rloc16_reply_callback(cti_connection_t NONNULL conn_ref, object_t reply, cti_status_t status_in)
+{
+    cti_rloc16_reply_t callback = conn_ref->callback.rloc16_reply;
+    cti_status_t status = status_in;
+    object_t result_dictionary = NULL;
+    uint16_t rloc16 = 0;
+    if (status == kCTIStatus_NoError) {
+        status = cti_event_or_response_extract(reply, &result_dictionary);
+        // The reply format is
+        // commandData:  {response: "> e000
+        // Done", method: "OtCtlCmd"}
+        // where e000 is the rloc16 that we want to extract
+        if (status == kCTIStatus_NoError) {
+            const char *rloc16_str = xpc_dictionary_get_string(result_dictionary, "response");
+            char *endptr = "\n";
+            rloc16 = (uint16_t)strtol(&rloc16_str[2], &endptr, 16);
+        }
+    }
+    if (callback != NULL) {
+        INFO("[CX%d] calling callback for %p", conn_ref->serial, conn_ref);
+        callback(conn_ref->context, rloc16, status);
+    } else {
+        INFO("[CX%d] not calling callback for %p", conn_ref->serial, conn_ref);
+    }
+}
+
+cti_status_t
+cti_get_rloc16_(srp_server_t *UNUSED server, cti_connection_t *ref,
+                void *NULLABLE context, cti_rloc16_reply_t NONNULL callback,
+                run_context_t NULLABLE client_queue, const char *file, int line)
+{
+    cti_callback_t app_callback;
+    app_callback.rloc16_reply = callback;
+    cti_status_t errx;
+    object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
+    xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
+    xpc_dictionary_set_string(dict, "method", "OtCtlCmd");
+    xpc_dictionary_set_string(dict, "otctl_cmd", "rloc16");
+
+    errx = setup_for_command(ref, client_queue, "get_rloc16", NULL, NULL, dict, "WpanctlCmd",
+                             context, app_callback, cti_internal_rloc16_reply_callback, false, file, line);
+    INFO("get_rloc16 result %d", errx);
+    xpc_release(dict);
+
+    return errx;
+}
+
+static void
+cti_internal_wed_reply_callback(cti_connection_t NONNULL conn_ref, object_t reply, cti_status_t status_in)
+{
+    cti_wed_reply_t callback = conn_ref->callback.wed_reply;
+    cti_service_vec_t *vec = NULL;
+    cti_status_t status = status_in;
+
+    const char *extended_mac = NULL;
+    const char *ml_eid = NULL;
+    bool added = false;
+
+    if (status == kCTIStatus_NoError) {
+        object_t result_dictionary = NULL;
+        status = cti_event_or_response_extract(reply, &result_dictionary);
+        if (status == kCTIStatus_NoError) {
+            object_t *value_array = xpc_dictionary_get_array(result_dictionary, "value");
+            if (value_array == NULL) {
+                INFO("[CX%d] wed status array not present in wed status event.", conn_ref->serial);
+                goto out;
+            } else {
+                size_t value_array_length = xpc_array_get_count(value_array);
+                for (size_t i = 0; i < value_array_length; i++) {
+                    object_t *elt = xpc_array_get_value(value_array, i);
+                    xpc_type_t type = xpc_get_type(elt);
+                    if (type != XPC_TYPE_DICTIONARY) {
+                        ERROR("non-dictionary element of value array");
+                        goto out;
+                    }
+                    const char *key = xpc_dictionary_get_string(elt, "key");
+                    if (key == NULL) {
+                        ERROR("no key in value array");
+                        goto out;
+                    }
+                    const char *value = xpc_dictionary_get_string(elt, "value");
+                    if (!strcmp(key, "extendedMACAddress")) {
+                        extended_mac = value;
+                    } else if (!strcmp(key, "mleid")) {
+                        ml_eid = value;
+                    } else if (!strcmp(key, "status")) {
+                        if (!strcmp(value, "wed_added")) {
+                            added = true;
+                        } else if (!strcmp(value, "wed_removed")) {
+                            added = false;
+                        } else {
+                            ERROR("unknown wed status " PUB_S_SRP, value);
+                            goto out;
+                        }
+                    } else {
+                        if (value == NULL) {
+                            INFO("unknown key in response: " PUB_S_SRP, key);
+                        } else {
+                            INFO("unknown key " PUB_S_SRP " with value " PRI_S_SRP, key, value);
+                        }
+                    }
+                }
+            }
+        }
+    }
+out:
+    if (callback != NULL) {
+        if (added && (ml_eid == NULL || extended_mac == NULL)) {
+            added = false;
+        }
+        INFO("[CX%d] calling callback for %p", conn_ref->serial, conn_ref);
+        callback(conn_ref->context, extended_mac, ml_eid, added, status);
+    }
+    if (vec != NULL) {
+        RELEASE_HERE(vec, cti_service_vec);
+    }
+}
+
+cti_status_t
+cti_track_wed_status_(srp_server_t *UNUSED server, cti_connection_t *ref, void *NULLABLE context,
+                      cti_wed_reply_t NONNULL callback, run_context_t NULLABLE client_queue,
+                      const char *file, int line)
+{
+    cti_callback_t app_callback;
+    app_callback.wed_reply = callback;
+    cti_status_t errx;
+    object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
+    xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
+    xpc_dictionary_set_string(dict, "method", "PropGet");
+    xpc_dictionary_set_string(dict, "property_name", "WakeOnDeviceConnectionStatus");
+
+    errx = setup_for_command(ref, client_queue, "get_wed_status", "WakeOnDeviceConnectionStatus", NULL, dict, "WpanctlCmd",
+                             context, app_callback, cti_internal_wed_reply_callback, false, file, line);
+    xpc_release(dict);
+
+    return errx;
+}
+
+cti_status_t
+cti_track_neighbor_ml_eid_(srp_server_t *UNUSED server, cti_connection_t *ref, void *NULLABLE context,
+                           cti_string_property_reply_t NONNULL callback, run_context_t NULLABLE client_queue,
+                           const char *file, int line)
+{
+    cti_callback_t app_callback;
+    app_callback.string_property_reply = callback;
+    cti_status_t errx;
+    object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+
+    xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
+    xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
+    xpc_dictionary_set_string(dict, "method", "PropGet");
+    xpc_dictionary_set_string(dict, "property_name", "ThreadNeighborMeshLocalAddress");
+
+    errx = setup_for_command(ref, client_queue, "get_neighbor_ml_eid", "ThreadNeighborMeshLocalAddress", "ThreadNeighborMeshLocalAddress", dict, "WpanctlCmd",
+                             context, app_callback, cti_internal_string_event_reply, false, file, line);
+    xpc_release(dict);
+
+    return errx;
+}
+
+cti_status_t
+cti_add_ml_eid_mapping_(srp_server_t *UNUSED server, void *NULLABLE context,
+                        cti_reply_t NONNULL callback, run_context_t NULLABLE client_queue,
+                        struct in6_addr *omr_addr, struct in6_addr *ml_eid, const char *hostname,
+                        const char *file, int line)
+{
+    cti_callback_t app_callback;
+    app_callback.reply = callback;
+    cti_status_t errx;
+    object_t dict = xpc_dictionary_create(NULL, NULL, 0);
+    char addrbuf[INET6_ADDRSTRLEN];
+
+    xpc_dictionary_set_string(dict, "interface", "org.wpantund.v1");
+    xpc_dictionary_set_string(dict, "path", "/org/wpantund/utun2");
+    xpc_dictionary_set_string(dict, "method", "UpdateAccessoryData");
+    inet_ntop(AF_INET6, omr_addr, addrbuf, sizeof(addrbuf));
+    xpc_dictionary_set_string(dict, "ipaddr_add", addrbuf);
+    inet_ntop(AF_INET6, ml_eid, addrbuf, sizeof(addrbuf));
+    xpc_dictionary_set_string(dict, "ipaddr_lookup", addrbuf);
+    xpc_dictionary_set_string(dict, "host_info", hostname);
+
+    errx = setup_for_command(NULL, client_queue, "add_mle_mapping", NULL, NULL, dict, "WpanctlCmd",
+                             context, app_callback, cti_internal_reply_callback, false, file, line);
+    xpc_release(dict);
+
+    return errx;
+}
 
 
 cti_status_t
-cti_events_discontinue(cti_connection_t connection)
+cti_events_discontinue(cti_connection_t ref)
 {
-    if (connection->io_context != NULL) {
-        cti_connection_close(connection);
+    if (ref->connection != NULL) {
+        INFO("[CX%d] canceling connection %p", ref->serial, ref->connection);
+        xpc_connection_cancel(ref->connection);
     }
-    cti_connection_release(connection);
+    // This is releasing the caller's reference.
+    cti_connection_release(ref);
     return kCTIStatus_NoError;
 }
-static void
-cti_role_event_parse(cti_connection_t connection)
-{
-    uint8_t role;
-
-    // And statement will fail as soon as anything fails to parse.
-    if (cti_connection_u8_parse(connection, &role) &&
-        cti_connection_parse_done(connection))
-    {
-        INFO("%d", role);
-        connection->callback.network_node_type_reply(connection, role, kCTIStatus_NoError);
-    }
-}
-
-static void
-cti_state_event_parse(cti_connection_t connection)
-{
-    uint8_t state;
-
-    // And statement will fail as soon as anything fails to parse.
-    if (cti_connection_u8_parse(connection, &state) &&
-        cti_connection_parse_done(connection))
-    {
-        INFO("%d", state);
-        connection->callback.state_reply(connection, state, kCTIStatus_NoError);
-    }
-}
-
-static void
-cti_uint64_property_event_parse(cti_connection_t connection)
-{
-    uint64_t property_value;
-
-    // And statement will fail as soon as anything fails to parse.
-    if (cti_connection_u64_parse(connection, &property_value) &&
-        cti_connection_parse_done(connection))
-    {
-        INFO("%" PRIx64, property_value);
-        connection->callback.uint64_property_reply(connection, property_value, kCTIStatus_NoError);
-    }
-}
-
-static void
-cti_service_event_parse(cti_connection_t connection)
-{
-    uint8_t service_count, i;
-
-    // And statement will fail as soon as anything fails to parse.
-    if (cti_connection_u8_parse(connection, &service_count)) {
-        cti_service_vec_t *vec = cti_service_vec_create(service_count);
-        if (vec == NULL) {
-            ERROR("cti_service_event_parse: no memory for service vector.");
-            return;
-        }
-        vec->num = 0;
-        for (i = 0; i < service_count; i++) {
-            uint32_t enterprise_number;
-            void *service_data = NULL;
-            uint16_t service_data_length;
-            void *server_data = NULL;
-            uint16_t server_data_length;
-            if (!cti_connection_u32_parse(connection, &enterprise_number) ||
-                !cti_connection_data_parse(connection, &service_data, &service_data_length) ||
-                !cti_connection_data_parse(connection, &server_data, &server_data_length))
-            {
-                if (service_data != NULL) {
-                    free(service_data);
-                }
-                cti_service_vec_release(vec);
-                return;
-            }
-            char service_data_buf[13], server_data_buf[55];
-            cti_service_t *service = NULL;
-            dump_to_hex(service_data, service_data_length, service_data_buf, sizeof(service_data_buf));
-            dump_to_hex(server_data, server_data_length, server_data_buf, sizeof(server_data_buf));
-            INFO("%" PRIu32 " %" PRIu16 "[ %s ] %" PRIu16 "[ %s ]",
-                 enterprise_number, service_data_length, service_data_buf, server_data_length, server_data_buf);
-            if (enterprise_number == THREAD_ENTERPRISE_NUMBER) {
-                if (service_data_length == 1) {
-                    service = cti_service_create(enterprise_number,
-                                                 ((uint8_t *)service_data)[0], 1, server_data, server_data_length, 0);
-                }
-            }
-            if (service == NULL) {
-                free(service_data);
-                free(server_data);
-            } else {
-                vec->services[vec->num++] = service;
-            }
-        }
-
-        if (!cti_connection_parse_done(connection)) {
-            cti_service_vec_release(vec);
-            return;
-        }
-
-        INFO("%zd", vec->num);
-        connection->callback.service_reply(connection, vec, kCTIStatus_NoError);
-    }
-}
-
-static void
-cti_prefix_event_parse(cti_connection_t connection)
-{
-    uint16_t prefix_count, i;
-
-    // And statement will fail as soon as anything fails to parse.
-    if (cti_connection_u16_parse(connection, &prefix_count)) {
-        if (prefix_count > 200) {
-            ERROR("cti_prefix_event_parse: bogus number of prefixes returned: %d", prefix_count);
-            cti_connection_close(connection);
-            return;
-        }
-        cti_prefix_vec_t *vec = cti_prefix_vec_create(prefix_count);
-        if (vec == NULL) {
-            ERROR("cti_prefix_event_parse: no memory for prefix vector.");
-            return;
-        }
-        vec->num = 0;
-        for (i = 0; i < prefix_count; i++) {
-            uint16_t flags;
-            uint8_t prefix_length;
-            void *prefix_data = NULL;
-            uint16_t prefix_data_length;
-            struct in6_addr *prefix_addr;
-            if (!cti_connection_u16_parse(connection, &flags) ||
-                !cti_connection_u8_parse(connection, &prefix_length) ||
-                !cti_connection_data_parse(connection, &prefix_data, &prefix_data_length))
-            {
-                cti_prefix_vec_release(vec);
-                return;
-            }
-            if (prefix_data_length != 8) {
-                ERROR("cti_prefix_event_parse: wrong prefix length: %d", prefix_data_length);
-                cti_prefix_vec_release(vec);
-                return;
-            }
-            prefix_addr = calloc(1, sizeof(*prefix_addr));
-            if (prefix_addr == NULL) {
-                ERROR("cti_prefix_event_parse: no memory for prefix data.");
-                cti_prefix_vec_release(vec);
-                return;
-            }
-            in6prefix_copy_from_data(prefix_addr, prefix_data, prefix_data_length);
-            free(prefix_data);
-            cti_prefix_t *prefix = cti_prefix_create(prefix_addr, prefix_length, 0, flags);
-            if (prefix == NULL) {
-                ERROR("cti_prefix_event_parse: no memory for prefix object.");
-                cti_prefix_vec_release(vec);
-                return;
-            }
-            vec->prefixes[vec->num++] = prefix;
-        }
-
-        if (!cti_connection_parse_done(connection)) {
-            cti_prefix_vec_release(vec);
-            return;
-        }
-
-        INFO("%zd", vec->num);
-        connection->callback.prefix_reply(connection, vec, kCTIStatus_NoError);
-    }
-}
-
-static void
-cti_message_parse(cti_connection_t connection)
-{
-    cti_connection_parse_start(connection);
-    if (!cti_connection_u16_parse(connection, &connection->message_type)) {
-        return;
-    }
-    switch(connection->message_type) {
-    case kCTIMessageType_Response:
-        cti_response_parse(connection);
-        break;
-    case kCTIMessageType_TunnelNameResponse:
-        cti_tunnel_response_parse(connection);
-        break;
-    case kCTIMessageType_StateEvent:
-        cti_state_event_parse(connection);
-        break;
-    case kCTIMessageType_UInt64PropEvent:
-        cti_uint64_property_event_parse(connection);
-        break;
-    case kCTIMessageType_RoleEvent:
-        cti_role_event_parse(connection);
-        break;
-    case kCTIMessageType_ServiceEvent:
-        cti_service_event_parse(connection);
-        break;
-    case kCTIMessageType_PrefixEvent:
-        cti_prefix_event_parse(connection);
-        break;
-    }
-}
 
 // Local Variables:
 // mode: C
diff --git a/ServiceRegistration/cti-services.h b/ServiceRegistration/cti-services.h
index 068750f..1729ef7 100644
--- a/ServiceRegistration/cti-services.h
+++ b/ServiceRegistration/cti-services.h
@@ -1,6 +1,6 @@
 /* cti-services.h
  *
- * Copyright (c) 2020-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2020-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -171,7 +171,6 @@
  *
  * status:	   Will be kCTIStatus_NoError on success, otherwise will indicate the
  * 			   failure that occurred.
- *
  */
 
 typedef void
@@ -179,8 +178,8 @@
 
 /* cti_string_property_reply: Callback for get calls that fetch a string property
  *
- * Called exactly once in response to a cti_get_tunnel_name() or cti_get_on_link_prefix() call, either with an error or
- * with a string containing the response.
+ * Called exactly once in response to (for example) a cti_get_tunnel_name() or cti_get_on_link_prefix() call, either
+ * with an error or with a string containing the response.
  *
  * cti_reply parameters:
  *
@@ -191,7 +190,6 @@
  *
  * status:	      Will be kCTIStatus_NoError on success, otherwise will indicate the
  * 			      failure that occurred.
- *
  */
 
 typedef void
@@ -214,7 +212,6 @@
  * return value:   Returns kCTIStatus_NoError when no error otherwise returns an error code indicating the
  *                 error that occurred. Note: A return value of kCTIStatus_NoError does not mean that the
  *                 request succeeded, merely that it was successfully started.
- *
  */
 
 #define cti_get_tunnel_name(server, context, callback, client_queue) \
@@ -223,6 +220,31 @@
 cti_get_tunnel_name_(srp_server_t *NULLABLE server, void *NULLABLE context, cti_string_property_reply_t NONNULL callback,
                      run_context_t NULLABLE client_queue, const char *NONNULL file, int line);
 
+/* cti_track_neighbor_ml_eid
+ *
+ * Track changes in our neighbor's mesh-local address (ML-EID). This makes sense when acting as an end device, not as a
+ * router.  The ML-EID tunnel is passed to the reply callback if the request succeeds; otherwise an error is either
+ * returned immediately or returned to the callback.
+ *
+ * context:        An anonymous pointer that will be passed along to the callback when
+ *                 an event occurs.
+ *
+ * callback:       CallBack function for the client that indicates success or failure.
+ *
+ * client_queue:   Queue the client wants to schedule the callback on
+ *
+ * return value:   Returns kCTIStatus_NoError when no error otherwise returns an error code indicating the
+ *                 error that occurred. Note: A return value of kCTIStatus_NoError does not mean that the
+ *                 request succeeded, merely that it was successfully started.
+ */
+
+#define cti_track_neighbor_ml_eid(server, ref, context, callback, client_queue) \
+    cti_track_neighbor_ml_eid_(server, ref, context, callback, client_queue, __FILE__, __LINE__)
+DNS_SERVICES_EXPORT cti_status_t
+cti_track_neighbor_ml_eid_(srp_server_t *NULLABLE server, cti_connection_t NULLABLE *NULLABLE ref,
+                           void *NULLABLE context, cti_string_property_reply_t NONNULL callback,
+                           run_context_t NULLABLE client_queue, const char *NONNULL file, int line);
+
 /* cti_get_mesh_local_prefix
  *
  * Get the mesh_local IPv6 prefix that is in use on the Thread mesh. The prefix is passed to the reply callback if the
@@ -238,13 +260,12 @@
  * return value:   Returns kCTIStatus_NoError when no error otherwise returns an error code indicating the
  *                 error that occurred. Note: A return value of kCTIStatus_NoError does not mean that the
  *                 request succeeded, merely that it was successfully started.
- *
  */
 
-#define cti_get_mesh_local_prefix(server, ref, context, callback, client_queue) \
-    cti_get_mesh_local_prefix_(server, ref, context, callback, client_queue, __FILE__, __LINE__)
+#define cti_get_mesh_local_prefix(server, context, callback, client_queue) \
+    cti_get_mesh_local_prefix_(server, context, callback, client_queue, __FILE__, __LINE__)
 DNS_SERVICES_EXPORT cti_status_t
-cti_get_mesh_local_prefix_(srp_server_t *NULLABLE server, cti_connection_t NULLABLE *NULLABLE ref,
+cti_get_mesh_local_prefix_(srp_server_t *NULLABLE server,
                            void *NULLABLE context, cti_string_property_reply_t NONNULL callback,
                            run_context_t NULLABLE client_queue, const char *NONNULL file, int line);
 
@@ -253,9 +274,6 @@
  * Get the mesh_local IPv6 address that is in use on this device on the Thread mesh. The address is passed to the reply
  * callback if the request succeeds; otherwise an error is either returned immediately or returned to the callback.
  *
- * ref:            A pointer to a reference to the connection is stored through ref if ref is not NULL.
- *                 When events are no longer needed, call cti_discontinue_events() on the returned pointer.
- *
  * context:        An anonymous pointer that will be passed along to the callback when
  *                 an event occurs.
  *
@@ -266,13 +284,12 @@
  * return value:   Returns kCTIStatus_NoError when no error otherwise returns an error code indicating the
  *                 error that occurred. Note: A return value of kCTIStatus_NoError does not mean that the
  *                 request succeeded, merely that it was successfully started.
- *
  */
 
-#define cti_get_mesh_local_address(server, ref, context, callback, client_queue) \
-    cti_get_mesh_local_address_(server, ref, context, callback, client_queue, __FILE__, __LINE__)
+#define cti_get_mesh_local_address(server, context, callback, client_queue) \
+    cti_get_mesh_local_address_(server, context, callback, client_queue, __FILE__, __LINE__)
 DNS_SERVICES_EXPORT cti_status_t
-cti_get_mesh_local_address_(srp_server_t *NULLABLE server, cti_connection_t NULLABLE *NULLABLE ref,
+cti_get_mesh_local_address_(srp_server_t *NULLABLE server,
                             void *NULLABLE context, cti_string_property_reply_t NONNULL callback,
                             run_context_t NULLABLE client_queue, const char *NONNULL file, int line);
 
@@ -375,7 +392,6 @@
  *
  * status:	          Will be kCTIStatus_NoError if the service list request is successful, or
  *                    will indicate the failure that occurred.
- *
  */
 
 typedef void
@@ -410,7 +426,6 @@
  * return value:   Returns kCTIStatus_NoError when no error otherwise returns an error code indicating
  *                 the error that occurred. Note: A return value of kCTIStatus_NoError does not mean
  *                 that the request succeeded, merely that it was successfully started.
- *
  */
 
 #define cti_get_service_list(server, ref, context, callback, client_queue) \
@@ -513,7 +528,6 @@
  *
  * status:	          Will be kCTIStatus_NoError if the prefix list request is successful, or
  *                    will indicate the failure that occurred.
- *
  */
 
 typedef void
@@ -543,7 +557,6 @@
  * return value:   Returns kCTIStatus_NoError when no error otherwise returns an error code indicating
  *                 the error that occurred. Note: A return value of kCTIStatus_NoError does not mean
  *                 that the request succeeded, merely that it was successfully started.
- *
  */
 
 #define cti_get_prefix_list(server, ref, context, callback, client_queue) \
@@ -571,7 +584,6 @@
  *
  * status:	          Will be kCTIStatus_NoError if the prefix list request is successful, or
  *                    will indicate the failure that occurred.
- *
  */
 
 typedef void
@@ -627,7 +639,6 @@
  *
  * status:	          Will be kCTIStatus_NoError if the partition ID request is successful, or will indicate the failure
  *                    that occurred.
- *
  */
 
 typedef void
@@ -655,7 +666,6 @@
  * return value:   Returns kCTIStatus_NoError when no error otherwise returns an error code indicating
  *                 the error that occurred. Note: A return value of kCTIStatus_NoError does not mean
  *                 that the request succeeded, merely that it was successfully started.
- *
  */
 
 #define cti_get_partition_id(server, ref, context, callback, client_queue) \
@@ -687,7 +697,6 @@
  * return value:   Returns kCTIStatus_NoError when no error otherwise returns an error code indicating
  *                 the error that occurred. Note: A return value of kCTIStatus_NoError does not mean
  *                 that the request succeeded, merely that it was successfully started.
- *
  */
 
 #define cti_get_extended_pan_id(server, ref, context, callback, client_queue) \
@@ -716,7 +725,6 @@
  *
  * status:	          Will be kCTIStatus_NoError if the partition ID request is successful, or will indicate the failure
  *                    that occurred.
- *
  */
 
 typedef void
@@ -744,7 +752,6 @@
  * return value:   Returns kCTIStatus_NoError when no error otherwise returns an error code indicating
  *                 the error that occurred. Note: A return value of kCTIStatus_NoError does not mean
  *                 that the request succeeded, merely that it was successfully started.
- *
  */
 
 #define cti_get_network_node_type(server, ref, context, callback, client_queue) \
@@ -785,7 +792,6 @@
  * return value:      Returns kCTIStatus_NoError when no error otherwise returns an error code indicating
  *                    the error that occurred. Note: A return value of kCTIStatus_NoError does not mean
  *                    that the request succeeded, merely that it was successfully started.
- *
  */
 
 #define cti_add_service(server, context, callback, client_queue, \
@@ -855,7 +861,6 @@
  * return value:   Returns kCTIStatus_NoError when no error otherwise returns an error code indicating
  *                 the error that occurred. Note: A return value of kCTIStatus_NoError does not mean
  *                 that the request succeeded, merely that it was successfully started.
- *
  */
 
 #define cti_add_prefix(server, context, callback, client_queue, \
@@ -888,7 +893,6 @@
  * return value:   Returns kCTIStatus_NoError when no error otherwise returns an error code indicating
  *                 the error that occurred. Note: A return value of kCTIStatus_NoError does not mean
  *                 that the request succeeded, merely that it was successfully started.
- *
  */
 
 #define cti_remove_prefix(server, context, callback, client_queue, prefix, prefix_length) \
@@ -927,7 +931,6 @@
  * return value:   Returns kCTIStatus_NoError when no error otherwise returns an error code indicating
  *                 the error that occurred. Note: A return value of kCTIStatus_NoError does not mean
  *                 that the request succeeded, merely that it was successfully started.
- *
  */
 
 #define cti_add_route(server, context, callback, client_queue, \
@@ -962,7 +965,6 @@
  * return value:   Returns kCTIStatus_NoError when no error otherwise returns an error code indicating
  *                 the error that occurred. Note: A return value of kCTIStatus_NoError does not mean
  *                 that the request succeeded, merely that it was successfully started.
- *
  */
 
 #define cti_remove_route(server, context, callback, client_queue, prefix, prefix_length, domain_id) \
@@ -1071,7 +1073,6 @@
  *
  * status:	          Will be kCTIStatus_NoError if the offmesh route list request is successful, or
  *                    will indicate the failure that occurred.
- *
  */
 
 typedef void
@@ -1098,7 +1099,6 @@
  * return value:   Returns kCTIStatus_NoError when no error otherwise returns an error code indicating
  *                 the error that occurred. Note: A return value of kCTIStatus_NoError does not mean
  *                 that the request succeeded, merely that it was successfully started.
- *
  */
 
 #define cti_get_offmesh_route_list(server, ref, context, callback, client_queue) \
@@ -1128,7 +1128,6 @@
  *
  * status:	          Will be kCTIStatus_NoError if the onmesh prefix list request is successful, or
  *                    will indicate the failure that occurred.
- *
  */
 
 typedef void
@@ -1155,7 +1154,6 @@
  * return value:   Returns kCTIStatus_NoError when no error otherwise returns an error code indicating
  *                 the error that occurred. Note: A return value of kCTIStatus_NoError does not mean
  *                 that the request succeeded, merely that it was successfully started.
- *
  */
 
 #define cti_get_onmesh_prefix_list(server, ref, context, callback, client_queue) \
@@ -1184,7 +1182,6 @@
  *
  * status:            Will be kCTIStatus_NoError if the rloc16 request is successful, or will indicate the failure
  *                    that occurred.
- *
  */
 typedef void
 (*cti_rloc16_reply_t)(void *NULLABLE context, uint16_t rloc16, cti_status_t status);
@@ -1208,7 +1205,6 @@
  * return value:   Returns kCTIStatus_NoError when no error otherwise returns an error code indicating
  *                 the error that occurred. Note: A return value of kCTIStatus_NoError does not mean
  *                 that the request succeeded, merely that it was successfully started.
- *
  */
 #define cti_get_rloc16(server, ref, context, callback, client_queue) \
     cti_get_rloc16_(server, ref, context, callback, client_queue, __FILE__, __LINE__)
@@ -1218,6 +1214,158 @@
                 void *NULLABLE context, cti_rloc16_reply_t NONNULL callback,
                 run_context_t NULLABLE client_queue, const char *NONNULL file, int line);
 
+/* cti_active_data_set_change_reply: Callback from cti_get_active_data_set_change()
+ *
+ * Called when an error occurs during processing of the cti_get_active_data_set_change call, or when the active data set
+ * is updated.
+ *
+ * In the case of an error, the callback will not be called again, and the caller is responsible for
+ * releasing the connection state and restarting if needed.
+ *
+ * The callback will be called whenever the active data set changes.
+ *
+ * cti_active_data_set_change_reply parameters:
+ *
+ * context:           The context that was passed to the cti call to which this is a callback.
+ *
+ * status:            Will be kCTIStatus_NoError if the active_data_set_change request is successful, or will indicate the failure
+ *                    that occurred.
+ */
+typedef void
+(*cti_reply_t)(void *NULLABLE context, cti_status_t status);
+
+/* cti_get_active_data_set_change
+ *
+ * Requests wpantund to immediately send the active_data_set_change of the local device. Whenever the ACTIVE_DATA_SET_CHANGE
+ * changes, the callback will be called again with the new ACTIVE_DATA_SET_CHANGE.  A return value of
+ * kCTIStatus_NoError means that the caller can expect the reply callback to be called at least once.  Any
+ * other error means that the request could not be sent, and the callback will never be called.
+ *
+ * ref:            A pointer to a reference to the connection is stored through ref if ref is not NULL.
+ *                 When events are no longer needed, call cti_discontinue_events() on the returned pointer.
+ *
+ * context:        An anonymous pointer that will be passed along to the callback when
+ *                 an event occurs.
+ * callback:       CallBack function for the client that indicates success or failure.
+ *
+ * client_queue:   Queue the client wants to schedule the callback on
+ *
+ * return value:   Returns kCTIStatus_NoError when no error otherwise returns an error code indicating
+ *                 the error that occurred. Note: A return value of kCTIStatus_NoError does not mean
+ *                 that the request succeeded, merely that it was successfully started.
+ */
+#define cti_track_active_data_set(server, ref, context, callback, client_queue) \
+    cti_track_active_data_set_(server, ref, context, callback, client_queue, __FILE__, __LINE__)
+
+DNS_SERVICES_EXPORT cti_status_t
+cti_track_active_data_set_(srp_server_t *NULLABLE server, cti_connection_t NULLABLE *NULLABLE ref,
+                void *NULLABLE context, cti_reply_t NONNULL callback,
+                run_context_t NULLABLE client_queue, const char *NONNULL file, int line);
+
+/* cti_wed_change_reply: Callback from cti_get_wed_change()
+ *
+ * Called when an error occurs during processing of the cti_get_wed_change call, or when the active data set
+ * is updated.
+ *
+ * In the case of an error, the callback will not be called again, and the caller is responsible for
+ * releasing the connection state and restarting if needed.
+ *
+ * The callback will be called whenever the active data set changes.
+ *
+ * cti_wed_change_reply parameters:
+ *
+ * context:           The context that was passed to the cti call to which this is a callback.
+ *
+ * status:            Will be kCTIStatus_NoError if the wed_change request is successful, or will indicate the failure
+ *                    that occurred.
+ */
+typedef void
+(*cti_reply_t)(void *NULLABLE context, cti_status_t status);
+
+/* cti_wed_reply: Callback for get calls that fetch the current WED status
+ *
+ * Called one or more times in response to a cti_track_wed_status() call, either with an error or with the current WED status.
+ *
+ * cti_wed_reply parameters:
+ *
+ * context:       The context that was passed to the cti service call to which this is a callback.
+ *
+ * ext_address:   If error is kCTIStatus_NoError, a string containing the name of the ext address.
+ *
+ * ml_eid:        If error is kCTIStatus_NoError, a string containing the ML-EID
+ *
+ * added:         If true, this was added; otherwise it was removed.
+ *
+ * status:	      Will be kCTIStatus_NoError on success, otherwise will indicate the
+ * 			      failure that occurred.
+ */
+
+typedef void
+(*cti_wed_reply_t)(void *NULLABLE context, const char *NULLABLE ext_address, const char *NULLABLE ml_eid, bool added,
+                   cti_status_t status);
+
+/* cti_get_wed_change
+ *
+ * Requests wpantund to immediately send the WED attachment status of the local device. Whenever the WED attachment
+ * status changes, the callback will be called again with the new status.  A return value of kCTIStatus_NoError means
+ * that the caller can expect the reply callback to be called at least once.  Any other error means that the request
+ * could not be sent, and the callback will never be called.
+ *
+ * ref:            A pointer to a reference to the connection is stored through ref if ref is not NULL.
+ *                 When events are no longer needed, call cti_discontinue_events() on the returned pointer.
+ *
+ * context:        An anonymous pointer that will be passed along to the callback when
+ *                 an event occurs.
+ *
+ * callback:       CallBack function for the client that indicates success or failure, and on success
+ *                 indicates attachment to a Wake-on End Device (WED).
+ *
+ * client_queue:   Queue the client wants to schedule the callback on
+ *
+ * return value:   Returns kCTIStatus_NoError when no error otherwise returns an error code indicating
+ *                 the error that occurred. Note: A return value of kCTIStatus_NoError does not mean
+ *                 that the request succeeded, merely that it was successfully started.
+ */
+#define cti_track_wed_status(server, ref, context, callback, client_queue) \
+    cti_track_wed_status_(server, ref, context, callback, client_queue, __FILE__, __LINE__)
+
+DNS_SERVICES_EXPORT cti_status_t
+cti_track_wed_status_(srp_server_t *NULLABLE server, cti_connection_t NULLABLE *NULLABLE ref,
+                      void *NULLABLE context, cti_wed_reply_t NONNULL callback,
+                      run_context_t NULLABLE client_queue, const char *NONNULL file, int line);
+
+/* cti_add_ml_eid_mapping
+ *
+ * Provides the thread daemon with a mapping between an IP address registered by an SRP client and
+ * the source address from which the registration was received, which will be the client's ml-eid.
+ * Additionally provides the client's hostname.
+ *
+ * context:        An anonymous pointer that will be passed along to the callback when
+ *                 an event occurs.
+ *
+ * callback:       CallBack function for the client that indicates success or failure, and on success
+ *                 indicates that the update was accepted by the thread daemon.
+ *
+ * omr_addr:       IP address on the OMR prefix registered by the client.
+ *
+ * ml_eid:         IP address on the mesh-local prefix of the client.
+ *
+ * client_queue:   Queue the client wants to schedule the callback on
+ *
+ *
+ * return value:   Returns kCTIStatus_NoError when no error otherwise returns an error code indicating
+ *                 the error that occurred. Note: A return value of kCTIStatus_NoError does not mean
+ *                 that the request succeeded, merely that it was successfully started.
+ */
+#define cti_add_ml_eid_mapping(server, context, callback, client_queue, omr_addr, ml_eid, hostname) \
+    cti_add_ml_eid_mapping_(server, context, callback, client_queue, omr_addr, ml_eid, hostname, __FILE__, __LINE__)
+
+DNS_SERVICES_EXPORT cti_status_t
+cti_add_ml_eid_mapping_(srp_server_t *NULLABLE server, void *NULLABLE context,
+                        cti_reply_t NONNULL callback, run_context_t NULLABLE client_queue,
+                        struct in6_addr *NONNULL omr_addr, struct in6_addr *NONNULL ml_eid,
+                        const char *NONNULL hostname, const char *NONNULL file, int line);
+
 /* cti_events_discontinue
  *
  * Requests that the CTI library stop delivering events on the specified connection.   The connection will have
@@ -1237,6 +1385,7 @@
     cti_offmesh_route_reply_t NONNULL offmesh_route_reply;
     cti_onmesh_prefix_reply_t NONNULL onmesh_prefix_reply;
     cti_rloc16_reply_t NONNULL rloc16_reply;
+    cti_wed_reply_t NONNULL wed_reply;
 } cti_callback_t;
 
 #endif /* __CTI_SERVICES_H__ */
diff --git a/ServiceRegistration/dns-msg.h b/ServiceRegistration/dns-msg.h
index 3e13498..adf3b6a 100644
--- a/ServiceRegistration/dns-msg.h
+++ b/ServiceRegistration/dns-msg.h
@@ -1,6 +1,6 @@
 /* dns-msg.h
  *
- * Copyright (c) 2018-2021 Apple Inc. All rights reserved.
+ * Copyright (c) 2018-2023 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -140,6 +140,17 @@
     uint8_t *NONNULL key;
 };
 
+typedef struct dns_rdata_soa dns_rdata_soa_t;
+struct dns_rdata_soa {
+    dns_label_t *NONNULL mname;
+    dns_label_t *NONNULL rname;
+    uint32_t serial;
+    uint32_t refresh;
+    uint32_t retry;
+    uint32_t expire;
+    uint32_t minimum;
+};
+
 typedef struct dns_rr dns_rr_t;
 struct dns_rr {
     dns_label_t *NONNULL name;
@@ -157,6 +168,7 @@
         dns_rdata_txt_t txt;
         dns_rdata_sig_t sig;
         dns_rdata_key_t key;
+        dns_rdata_soa_t soa;
     } data;
 };
 
@@ -235,7 +247,7 @@
 #define dns_rcode_nxrrset      8 // [RFC2136] RR Set that should exist does not
 #define dns_rcode_notauth      9 // [RFC2136] Server Not Authoritative for zone, or [RFC2845] Not Authorized
 #define dns_rcode_notzone     10 // [RFC2136] Name not contained in zone
-#define dns_rcode_dsotypeni   11 // [RFCTBD draft-ietf-dnsop-session-signal] DSO-Type Not Implemented
+#define dns_rcode_dsotypeni   11 // [RFC8490] DSO-Type Not Implemented
 #define dns_rcode_badvers     16 // [RFC6891] Bad OPT Version, or [RFC2845] TSIG Signature Failure
 #define dns_rcode_badkey      17 // [RFC2845] Key not recognized
 #define dns_rcode_badtime     18 // [RFC2845] Signature out of time window
@@ -315,8 +327,8 @@
 #define dns_rrtype_openpgpkey 61 // [RFC7929]   OpenPGP Key
 #define dns_rrtype_csync      62 // [RFC7477] Child-To-Parent Synchronization
 #define dns_rrtype_zonemd     63 // [RFC8976]
-#define dns_rrtype_svcb       64 // [draft-ietf-dnsop-svcb-https-10]
-#define dns_rrtype_https      65 // [draft-ietf-dnsop-svcb-https-10]
+#define dns_rrtype_svcb       64 // [RFC9460]
+#define dns_rrtype_https      65 // [RFC9460]
 #define dns_rrtype_spf        99 // [RFC7208]
 #define dns_rrtype_uinfo     100 // [IANA-Reserved]
 #define dns_rrtype_uid       101 // [IANA-Reserved]
@@ -453,6 +465,7 @@
 bool dns_u16_parse(const uint8_t *NONNULL buf, unsigned len, unsigned *NONNULL offp, uint16_t *NONNULL ret);
 bool dns_u32_parse(const uint8_t *NONNULL buf, unsigned len, unsigned *NONNULL offp, uint32_t *NONNULL ret);
 bool dns_u64_parse(const uint8_t *NONNULL buf, unsigned len, unsigned *NONNULL offp, uint64_t *NONNULL ret);
+size_t dns_rdata_dump_to_buf(dns_rr_t *NONNULL rr, char *NONNULL buf, size_t bufsize);
 #define dns_rdata_parse_data(rr, buf, offp, target, rdlen, rrstart) \
     dns_rdata_parse_data_(rr, buf, offp, target, rdlen, rrstart, __FILE__, __LINE__)
 bool dns_rdata_parse_data_(dns_rr_t *NONNULL rr, const uint8_t *NONNULL buf, unsigned *NONNULL offp,
diff --git a/ServiceRegistration/dnssd-client.c b/ServiceRegistration/dnssd-client.c
index 3d9d6a7..84b481f 100644
--- a/ServiceRegistration/dnssd-client.c
+++ b/ServiceRegistration/dnssd-client.c
@@ -1,6 +1,6 @@
 /* dnssd-client.c
  *
- * Copyright (c) 2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2023-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -59,18 +59,19 @@
 #include <network_information.h>
 
 #include <CoreUtils/CoreUtils.h>
+#include <mrc/private.h>
 #endif // IOLOOP_MACOS
 
 #include "srp.h"
 #include "dns-msg.h"
 #include "ioloop.h"
-#include "adv-ctl-server.h"
 #include "srp-crypto.h"
 
 #include "cti-services.h"
 #include "srp-gw.h"
 #include "srp-proxy.h"
 #include "srp-mdns-proxy.h"
+#include "adv-ctl-server.h"
 #include "dnssd-proxy.h"
 #include "srp-proxy.h"
 #include "route.h"
@@ -80,6 +81,7 @@
     dnssd_client_state_invalid,
     dnssd_client_state_startup,
     dnssd_client_state_not_client,
+    dnssd_client_state_probing,
     dnssd_client_state_client,
 } state_machine_state_t;
 #define state_machine_state_invalid dnssd_client_state_invalid
@@ -93,21 +95,30 @@
 #include "thread-tracker.h"
 #include "probe-srp.h"
 
+typedef enum {
+    dnssd_client_dns_service_type_invalid   = 0,
+    dnssd_client_dns_service_type_do53      = 1,
+    dnssd_client_dns_service_type_push      = 2,
+} dnssd_client_dns_service_type_t;
+
 struct dnssd_client {
     int ref_count;
     state_machine_header_t state_header;
     char *id;
     srp_server_t *server_state;
-    cti_connection_t ml_prefix_connection;
+    wakeup_t *wakeup_timer;
+    cti_connection_t active_data_set_connection;
     struct in6_addr mesh_local_prefix;
     bool have_mesh_local_prefix;
     bool first_time;
     bool canceled;
-    DNSRecordRef aaaa_record_ref, ns_record_ref;
     dnssd_txn_t *shared_txn;
     thread_service_t *published_service;
     DNSServiceRef shared_connection;
+    mrc_dns_service_registration_t dns_service_registration;        // DNS service registration handler.
+    dnssd_client_dns_service_type_t dns_service_registration_type;  // The type of the registered DNS service.
     int interface_index;
+    uint16_t dns_service_registration_port;                         // The port of the registered DNS service used.
 };
 
 static uint64_t dnssd_client_serial_number;
@@ -116,7 +127,8 @@
 dnssd_client_finalize(dnssd_client_t *client)
 {
     thread_service_release(client->published_service);
-
+    ioloop_wakeup_release(client->wakeup_timer);
+    free(client->state_header.name);
     free(client->id);
     free(client);
 }
@@ -131,6 +143,19 @@
 }
 
 static void
+dnssd_client_wait_expired(void *context)
+{
+    dnssd_client_t *client = context;
+    state_machine_event_t *event = state_machine_event_create(state_machine_event_type_timeout, NULL);
+    if (event == NULL) {
+        ERROR("unable to allocate event to deliver");
+        return;
+    }
+    state_machine_event_deliver(&client->state_header, event);
+    RELEASE_HERE(event, state_machine_event);
+}
+
+static void
 dnssd_client_service_tracker_callback(void *context)
 {
     dnssd_client_t *client = context;
@@ -199,50 +224,23 @@
     state_machine_event_deliver(&client->state_header, event);
     RELEASE_HERE(event, state_machine_event);
 fail:
+    RELEASE_HERE(client, dnssd_client); // was retained for callback, can only get one callback
     return;
 }
 
-static void
+static state_machine_state_t
 dnssd_client_remove_published_service(dnssd_client_t *client)
 {
-    if (client->shared_txn != NULL) {
-        ioloop_dnssd_txn_cancel(client->shared_txn);
-        ioloop_dnssd_txn_release(client->shared_txn);
-        client->shared_txn = NULL;
+    if (client->dns_service_registration != NULL) {
+        mrc_dns_service_registration_forget(&client->dns_service_registration);
     }
-    client->aaaa_record_ref = NULL;
-    client->ns_record_ref = NULL;
-    if (client->published_service != NULL) {
-        thread_service_release(client->published_service);
-        client->published_service = NULL;
-    }
-}
-
-static void
-dnssd_client_publish_failed(dnssd_client_t *client)
-{
-    dnssd_client_remove_published_service(client);
-    state_machine_event_t *event = state_machine_event_create(state_machine_event_type_daemon_disconnect, NULL);
-    if (event == NULL) {
-        ERROR("unable to allocate event to deliver");
-        return;
-    }
-    state_machine_event_deliver(&client->state_header, event);
-    RELEASE_HERE(event, state_machine_event);
-}
-
-static void
-dnssd_client_shared_connection_failed_callback(void *context, int UNUSED status)
-{
-    dnssd_client_t *client = context;
-    ERROR("daemon connection failed");
-    dnssd_client_publish_failed(client);
+    return dnssd_client_state_not_client;
 }
 
 static state_machine_state_t
 dnssd_client_service_unpublish(dnssd_client_t *client)
 {
-    if (client->aaaa_record_ref != NULL) {
+    if (client->dns_service_registration != NULL) {
         thread_service_note(client->id, client->published_service, "unpublishing service");
     }
     dnssd_client_remove_published_service(client);
@@ -250,111 +248,140 @@
     return dnssd_client_state_not_client;
 }
 
-static void dnssd_client_record_callback(DNSServiceRef UNUSED sdref, DNSRecordRef UNUSED rref,
-                                         DNSServiceFlags UNUSED flags, DNSServiceErrorType error_code, void *context)
+static void
+dnssd_client_dns_service_event_handler(const mrc_dns_service_registration_event_t mrc_event, const OSStatus event_err,
+                                       dnssd_client_t *client)
 {
-    dnssd_client_t *client = context;
+    state_machine_event_t *event = NULL;
+    bool want_event = false;
+    switch(mrc_event)
+    {
+    case mrc_dns_service_registration_event_started:
+        INFO("DNS service registration started");
+        break;
+    case mrc_dns_service_registration_event_interruption:
+        INFO("DNS service registration interrupted" );
+        break;
 
-    if (error_code != kDNSServiceErr_NoError) {
-        if (rref == client->aaaa_record_ref) {
-            ERROR("unable to register AAAA record: error code %d", error_code);
+    case mrc_dns_service_registration_event_invalidation:
+        want_event = true;
+        if (event_err) {
+            ERROR("DNS service registration invalidated with error: %d", (int)event_err);
         } else {
-            ERROR("unable to register NS record: error code %d", error_code);
+            INFO("DNS service registration gracefully invalidated");
         }
-        dnssd_client_publish_failed(client);
-        return;
+        event = state_machine_event_create(state_machine_event_type_dns_registration_invalidated, NULL);
+        break;
+    case mrc_dns_service_registration_event_connection_error:
+        want_event = true;
+        ERROR("Registered DNS Push connection failed on server with error: %d", (int)event_err);
+        event = state_machine_event_create(state_machine_event_type_dns_registration_bad_service, NULL);
+        break;
     }
-    if (rref == client->aaaa_record_ref) {
-        ERROR("successfully registered AAAA record");
-    } else {
-        ERROR("successfully registered NS record");
+
+    if (want_event) {
+        if (event == NULL) {
+            ERROR("unable to allocate event to deliver");
+        } else {
+            state_machine_event_deliver(&client->state_header, event);
+            RELEASE_HERE(event, state_machine_event);
+        }
+        // Either event should be the last event we get.
+        RELEASE_HERE(client, dnssd_client);
     }
 }
 
+
 static state_machine_state_t
 dnssd_client_service_publish(dnssd_client_t *client)
 {
-    int err;
-    dns_towire_state_t towire;
-    dns_wire_t message;
+    dnssd_client_service_unpublish(client);
+    state_machine_state_t ret = dnssd_client_state_not_client;
 
-    thread_service_note(client->id, client->published_service, "publishing service");
+    OSStatus err;
+    mdns_dns_service_definition_t do53_definition = NULL;
+    mdns_dns_push_service_definition_t push_definition = NULL;
+    mdns_domain_name_t domain_name = NULL;
+    mdns_address_t server_address = NULL;
 
-    // Set up advertisement for the DNSSD server:
+    domain_name = mdns_domain_name_create("default.service.arpa.", mdns_domain_name_create_opts_none, &err);
+    require_noerr_action(err, out, ERROR("failed to create default.service.arpa.: %{darwin.errno}d", (int)err));
 
-    // First we need a shared connection for DNSServiceRegisterRecord.
-    err = DNSServiceCreateConnection(&client->shared_connection);
-    if (err != kDNSServiceErr_NoError) {
-        ERROR("DNSServiceCreateConnection failed");
-        return dnssd_client_service_unpublish(client);
-    }
-    client->shared_txn = ioloop_dnssd_txn_add(client->shared_connection, client, dnssd_client_context_release,
-                                              dnssd_client_shared_connection_failed_callback);
-    if (client->shared_txn == NULL) {
-        return dnssd_client_service_unpublish(client);
-    }
-
-    //   Set up AAAA record for dnssd-server.local
-    uint8_t *aaaa_rdata;
+    const uint8_t *aaaa_data = NULL;
+    uint16_t port = 0;
+    // @TODO: Adds SRV service probing to determine the DoT port instead the default dns_service_registration_port.
     if (client->published_service->service_type == unicast_service) {
-        aaaa_rdata = &client->published_service->u.unicast.address.s6_addr[0];
+        aaaa_data = &client->published_service->u.unicast.address.s6_addr[0];
     } else if (client->published_service->service_type == anycast_service) {
-        aaaa_rdata = &client->published_service->u.anycast.address.s6_addr[0];
+        aaaa_data = &client->published_service->u.anycast.address.s6_addr[0];
+    }
+    port = client->dns_service_registration_port;
+    require_action(aaaa_data != NULL, out, ERROR("failed to get service address"));
+
+    SEGMENTED_IPv6_ADDR_GEN_SRP(aaaa_data, ipv6_addr_buf);
+    server_address = mdns_address_create_ipv6(aaaa_data, port, 0);
+    if (server_address == NULL) {
+        ERROR("failed to create address object -- address: " PRI_SEGMENTED_IPv6_ADDR_SRP,
+              SEGMENTED_IPv6_ADDR_PARAM_SRP(aaaa_data, ipv6_addr_buf));
+        goto out;
+    }
+    if (client->dns_service_registration_type == dnssd_client_dns_service_type_push) {
+        push_definition = mdns_dns_push_service_definition_create();
+        require_action(push_definition, out, ERROR("unable to allocate mdns_dns_push_service_definition object"));
+
+        err = mdns_dns_push_service_definition_add_domain(push_definition, domain_name);
+        require_noerr(err, out);
+
+        mdns_dns_push_service_definition_append_server_address(push_definition, server_address);
+
+        client->dns_service_registration = mrc_dns_service_registration_create_push(push_definition);
+        if (client->dns_service_registration) {
+            mrc_dns_service_registration_set_reports_connection_errors(client->dns_service_registration,
+                true);
+        }
+    } else if (client->dns_service_registration_type == dnssd_client_dns_service_type_do53) {
+        do53_definition = mdns_dns_service_definition_create();
+        require_action(do53_definition, out, ERROR("unable to allocate mdns_dns_service_definition object"));
+
+        err = mdns_dns_service_definition_add_domain(do53_definition, domain_name);
+        require_noerr(err, out);
+
+        err = mdns_dns_service_definition_append_server_address(do53_definition, server_address);
+        require_noerr(err, out);
+
+        client->dns_service_registration = mrc_dns_service_registration_create(do53_definition);
     } else {
-        ERROR("invalid service type for published service: %d", client->published_service->service_type);
-        return dnssd_client_service_unpublish(client);
+        ERROR("Unknown DNS service registration type: %d", client->dns_service_registration_type);
+        goto out;
     }
-    err = DNSServiceRegisterRecord(client->shared_connection, &client->aaaa_record_ref,
-                                   kDNSServiceFlagsKnownUnique, client->interface_index,
-                                   "dnssd-server.local", kDNSServiceType_AAAA, kDNSServiceClass_IN, 16, aaaa_rdata, 0,
-                                   dnssd_client_record_callback, client);
-    if (err != kDNSServiceErr_NoError) {
-        ERROR("DNSServiceRegisterRecord failed - record: dnssd-server.local AAAA ");
-        return dnssd_client_service_unpublish(client);
-    }
+    require_action(client->dns_service_registration != NULL, out, ERROR("failed to create DNS service registration"));
+    mrc_dns_service_registration_set_queue(client->dns_service_registration, dispatch_get_main_queue());
 
-    //   Set up default.service.arpa NS dnssd-server.local
-    memset(&towire, 0, sizeof(towire));
-    towire.message = &message;
-    towire.lim = &message.data[DNS_DATA_SIZE];
-    towire.p = message.data;
+    RETAIN_HERE(client, dnssd_client); // For the handler
+    mrc_dns_service_registration_set_event_handler(client->dns_service_registration,
+    ^(const mrc_dns_service_registration_event_t event, const OSStatus event_err)
+    {
+        dnssd_client_dns_service_event_handler(event, event_err, client);
+    });
+    INFO("Publishing dnssd client service -- domain: " PRI_DNS_NAME_SRP ", address: " PRI_SEGMENTED_IPv6_ADDR_SRP,
+        mdns_domain_name_get_presentation(domain_name), SEGMENTED_IPv6_ADDR_PARAM_SRP(aaaa_data, ipv6_addr_buf));
+    mrc_dns_service_registration_activate(client->dns_service_registration);
 
-    dns_full_name_to_wire(NULL, &towire, "dnssd-server.local.");
-    err = DNSServiceRegisterRecord(client->shared_connection, &client->ns_record_ref,
-                                   kDNSServiceFlagsKnownUnique | kDNSServiceFlagsForceMulticast,
-                                   client->interface_index,
-                                   "openthread.thread.home.arpa", kDNSServiceType_NS, kDNSServiceClass_IN,
-                                   towire.p - message.data, message.data, 0, dnssd_client_record_callback, client);
-    if (err != kDNSServiceErr_NoError) {
-        ERROR("DNSServiceRegisterRecord failed - record: " "openthread.thread.home.arpa" " NS " "dnssd-server.local");
-        return dnssd_client_service_unpublish(client);
-    }
-
-#if PUBLISH_LEGACY_BROWSING_DOMAIN_FOR_THREAD_DEVICE
-
-    memset(&towire, 0, sizeof(towire));
-    towire.message = &message;
-    towire.lim = &message.data[DNS_DATA_SIZE];
-    towire.p = message.data;
-
-    dns_full_name_to_wire(NULL, &towire, "thread.service.arpa.");
-    err = DNSServiceRegisterRecord(client->service_ref, &client->ptr_record_ref,
-                                   kDNSServiceFlagsKnownUnique, server_state->advertise_interface, AUTOMATIC_BROWSING_DOMAIN,
-                                   kDNSServiceType_PTR, kDNSServiceClass_IN, towire.p - message.data, message.data, 0,
-                                   dnssd_client_record_callback, client);
-    if (err != kDNSServiceErr_NoError) {
-        ERROR("DNSServiceRegisterRecord failed - record: " AUTOMATIC_BROWSING_DOMAIN " PTR " "openthread.thread.home.arpa");
-        return dnssd_client_service_unpublish(client);
-    }
-#endif
-
-    return dnssd_client_state_invalid;
+    ret = dnssd_client_state_invalid;
+out:
+    mdns_forget(&do53_definition);
+    mdns_forget(&push_definition);
+    mdns_forget(&domain_name);
+    mdns_forget(&server_address);
+    return ret;
 }
 
 static state_machine_state_t dnssd_client_action_startup(state_machine_header_t *state_header,
                                                          state_machine_event_t *event);
 static state_machine_state_t dnssd_client_action_not_client(state_machine_header_t *state_header,
                                                             state_machine_event_t *event);
+static state_machine_state_t dnssd_client_action_probing(state_machine_header_t *state_header,
+                                                         state_machine_event_t *event);
 static state_machine_state_t dnssd_client_action_client(state_machine_header_t *state_header,
                                                         state_machine_event_t *event);
 
@@ -367,6 +394,7 @@
     { SERVICE_PUB_NAME_DECL(invalid),                            NULL },
     { SERVICE_PUB_NAME_DECL(startup),                            dnssd_client_action_startup },
     { SERVICE_PUB_NAME_DECL(not_client),                         dnssd_client_action_not_client },
+    { SERVICE_PUB_NAME_DECL(probing),                            dnssd_client_action_probing },
     { SERVICE_PUB_NAME_DECL(client),                             dnssd_client_action_client },
 };
 #define DNSSD_CLIENT_NUM_STATES ((sizeof(dnssd_client_states)) / (sizeof(state_machine_decl_t)))
@@ -374,7 +402,7 @@
 #define STATE_MACHINE_HEADER_TO_CLIENT(state_header)                                       \
     if (state_header->state_machine_type != state_machine_type_dnssd_client) {             \
         ERROR("state header type isn't omr_client: %d", state_header->state_machine_type); \
-        return dnssd_client_state_invalid;												   \
+        return dnssd_client_state_invalid;                                                 \
     }                                                                                      \
     dnssd_client_t *client = state_header->state_object
 
@@ -403,8 +431,7 @@
     return should_be_client;
 }
 
-// We get into this state when there is no SRP service published on the Thread network other than our own.
-// If we stop publishing (because a BR-based service showed up), then we go to the probe state.
+// We start in this state and remain here until we get a mesh-local prefix.
 static state_machine_state_t
 dnssd_client_action_startup(state_machine_header_t *state_header, state_machine_event_t *event)
 {
@@ -429,44 +456,102 @@
         if (client->published_service != NULL) {
             dnssd_client_service_unpublish(client);
         }
+        return dnssd_client_state_invalid;
     }
 
     // We do the same thing here for any event we get, because we're just waiting for conditions to be right.
     if (dnssd_client_should_be_client(client)) {
-        thread_service_t *service;
-
-        // See if we now have a service that's been successfully probed; if so, publish it.
-        service = service_tracker_verified_service_get(client->server_state->service_tracker);
-        if (service != NULL) {
-            if (client->published_service != NULL) {
-                thread_service_release(client->published_service);
-            }
-            client->published_service = service;
-            thread_service_retain(client->published_service);
-            return dnssd_client_state_client;
-        }
-
-        // Check to see if we can start a new probe
-        service = service_tracker_unverified_service_get(client->server_state->service_tracker);
-        if (service != NULL) {
-            if (service->service_type == anycast_service) {
-                memcpy(&service->u.anycast.address, &client->mesh_local_prefix, 8);
-                memcpy(&service->u.anycast.address.s6_addr[8], thread_rloc_preamble, 6);
-                service->u.anycast.address.s6_addr[14] = service->rloc16 >> 8;
-                service->u.anycast.address.s6_addr[15] = service->rloc16 & 255;
-            }
-            probe_srp_service(service, client, dnssd_client_probe_callback);
-            RETAIN_HERE(client, dnssd_client); // For the callback
-            return dnssd_client_state_invalid;
-        }
-
-        // This should only ever happen if we get the service list update before the service publisher gets it.
-        INFO("no service to publish");
-        return dnssd_client_state_invalid;
+        return dnssd_client_state_probing;
     }
     return dnssd_client_state_invalid;
 }
 
+// We get into this state when we've determined we should be a client
+static state_machine_state_t
+dnssd_client_action_probing(state_machine_header_t *state_header, state_machine_event_t *event)
+{
+    STATE_MACHINE_HEADER_TO_CLIENT(state_header);
+    BR_STATE_ANNOUNCE(client, event);
+
+    thread_service_t *service;
+    srp_server_t *server_state = client->server_state;
+
+    // On arrival in this state, we start a timer. If we get to the publishing state, we cancel the timer. If the
+    // timer goes off when we're still probing, we tell the service publisher to publish any relevant cached
+    // services.  The point of this is that it's fairly common at least in testing that we want to be able to
+    // control an accessory after joining the Thread network /because/ the BR went down, and in this case the BR's
+    // published service may take 120 seconds to time out from the thread network data. Publishing the cached
+    // services can potentially help with this.
+    if (event == NULL) {
+        ioloop_add_wake_event(client->wakeup_timer, client, dnssd_client_wait_expired,
+                              dnssd_client_context_release, 5 * MSEC_PER_SEC); // Wait five seconds for probe to succeed.
+        RETAIN_HERE(client, dnssd_client); // for the wake event
+    }
+
+    // If we have been waiting for a second since we started probing and haven't succeeded, tell the service publisher
+    // to publish services.
+    else if (event->type == state_machine_event_type_timeout) {
+        if (server_state->service_publisher != NULL) {
+            INFO("server probe startup timeout expired--publishing cached data.");
+            service_publisher_re_advertise_matching(server_state->service_publisher);
+        }
+        return dnssd_client_state_invalid;
+    }
+
+    // If we no longer need to be a client, stop trying.
+    else {
+        if (!dnssd_client_should_be_client(client)) {
+            ioloop_cancel_wake_event(client->wakeup_timer);
+            return dnssd_client_state_not_client;
+        }
+    }
+
+    // See if we now have a service that's been successfully probed; if so, publish it.
+    service = service_tracker_verified_service_get(client->server_state->service_tracker);
+    if (service != NULL) {
+        if (client->published_service != NULL) {
+            thread_service_release(client->published_service);
+        }
+        client->published_service = service;
+        thread_service_retain(client->published_service);
+
+        // Tell the service publisher that we successfully probed a service.
+        INFO("server probe succeeded--unpublishing cached data.");
+        service_publisher_unadvertise_all(server_state->service_publisher);
+
+        // We no longer want a wakeup.  Note that this is the only way out of the probing state, so it's not
+        // possible to exit the state without canceling the timer here.
+        ioloop_cancel_wake_event(client->wakeup_timer);
+
+        // Publish DNS Push service pointing to probed service.
+        return dnssd_client_state_client;
+    }
+
+    // Check to see if we can start a new probe
+    service = service_tracker_unverified_service_get(client->server_state->service_tracker, unicast_service);
+    if (service != NULL) {
+        if (service->checking) {
+            service_tracker_thread_service_note(client->server_state->service_tracker, service,
+                                                " is still being probed");
+            return dnssd_client_state_invalid;
+        }
+        if (service->service_type == anycast_service) {
+            memcpy(&service->u.anycast.address, &client->mesh_local_prefix, 8);
+            memcpy(&service->u.anycast.address.s6_addr[8], thread_rloc_preamble, 6);
+            service->u.anycast.address.s6_addr[14] = service->rloc16 >> 8;
+            service->u.anycast.address.s6_addr[15] = service->rloc16 & 255;
+        }
+        RETAIN_HERE(client, dnssd_client); // For the probe
+        probe_srp_service(service, client, dnssd_client_probe_callback, dnssd_client_context_release);
+        return dnssd_client_state_invalid;
+    }
+
+    // This should only ever happen if we get the service list update before the service publisher gets it.
+    INFO("no service to publish");
+    return dnssd_client_state_invalid;
+}
+
+
 // We get into this state when we've published a service.
 static state_machine_state_t
 dnssd_client_action_client(state_machine_header_t *state_header, state_machine_event_t *event)
@@ -480,6 +565,18 @@
         return dnssd_client_state_invalid;
     }
 
+    if (event->type == state_machine_event_type_dns_registration_bad_service) {
+        if (client->published_service == NULL) {
+            INFO("bad service event received with no published service.");
+        } else {
+            client->published_service->ignore = true;  // Don't consider this service when deciding what to advertise
+            client->published_service->responding = false;
+            thread_service_release(client->published_service);
+            client->published_service = NULL;
+            return dnssd_client_state_probing; // Go back to probing.
+        }
+    }
+
     // This can happen if the daemon disconnects.
     if (client->published_service == NULL) {
         return dnssd_client_state_not_client;
@@ -502,10 +599,21 @@
 void
 dnssd_client_cancel(dnssd_client_t *client)
 {
-    service_tracker_callback_cancel(client->server_state->service_tracker, client);
-    thread_tracker_callback_cancel(client->server_state->thread_tracker, client);
-    cti_events_discontinue(client->ml_prefix_connection);
-    client->ml_prefix_connection = NULL;
+    if (client->server_state->service_tracker != NULL) {
+        service_tracker_cancel_probes(client->server_state->service_tracker);
+        service_tracker_callback_cancel(client->server_state->service_tracker, client);
+    }
+    if (client->server_state->thread_tracker != NULL) {
+        thread_tracker_callback_cancel(client->server_state->thread_tracker, client);
+    }
+    ioloop_cancel_wake_event(client->wakeup_timer);
+    if (client->active_data_set_connection != NULL) {
+        cti_events_discontinue(client->active_data_set_connection);
+        RELEASE_HERE(client, dnssd_client); // callback held reference
+        client->active_data_set_connection = NULL;
+    }
+    dnssd_client_remove_published_service(client);
+    state_machine_cancel(&client->state_header);
 }
 
 dnssd_client_t *
@@ -516,15 +624,22 @@
         return client;
     }
     RETAIN_HERE(client, dnssd_client);
+    client->wakeup_timer = ioloop_wakeup_create();
+    if (client->wakeup_timer == NULL) {
+        ERROR("wakeup timer alloc failed");
+        goto out;
+    }
 
     char client_id_buf[100];
-    snprintf(client_id_buf, sizeof(client_id_buf), "[SP%lld]", ++dnssd_client_serial_number);
+    snprintf(client_id_buf, sizeof(client_id_buf), "[DC%lld]", ++dnssd_client_serial_number);
     client->id = strdup(client_id_buf);
     if (client->id == NULL) {
         ERROR("no memory for client ID");
         goto out;
     }
     client->interface_index = -1;
+    client->dns_service_registration_type = dnssd_client_dns_service_type_push;
+    client->dns_service_registration_port = DNS_OVER_TLS_DEFAULT_PORT;
 
     if (!state_machine_header_setup(&client->state_header,
                                     client, client->id,
@@ -558,18 +673,47 @@
     dnssd_client_t *client = context;
     if (status != kCTIStatus_NoError) {
         INFO("didn't get tunnel name, error code %d", status);
-        return;
+        goto fail;
     }
     client->interface_index = if_nametoindex(name);
+fail:
+    RELEASE_HERE(client, dnssd_client); // was retained for callback, can only get one callback
+}
+
+static void
+dnssd_client_active_data_set_changed_callback(void *context, cti_status_t status)
+{
+    dnssd_client_t *client = context;
+
+    if (status != kCTIStatus_NoError) {
+        ERROR("error %d", status);
+        RELEASE_HERE(client, dnssd_client); // no more callbacks
+        cti_events_discontinue(client->active_data_set_connection);
+        client->active_data_set_connection = NULL;
+        return;
+    }
+
+    status = cti_get_mesh_local_prefix(client->server_state, client, dnssd_client_get_mesh_local_prefix_callback, NULL);
+    if (status != kCTIStatus_NoError) {
+        ERROR("cti_get_mesh_local_prefix failed with status %d", status);
+    } else {
+        RETAIN_HERE(client, dnssd_client); // for mesh-local callback
+    }
+    status = cti_get_tunnel_name(client->server_state, client, dnssd_client_get_tunnel_name_callback, NULL);
+    if (status != kCTIStatus_NoError) {
+        ERROR("cti_get_tunnel_name failed with status %d", status);
+    } else {
+        RETAIN_HERE(client, dnssd_client); // for tunnel name callback
+    }
 }
 
 void
 dnssd_client_start(dnssd_client_t *client)
 {
-    cti_get_mesh_local_prefix(client->server_state, &client->ml_prefix_connection, client,
-                              dnssd_client_get_mesh_local_prefix_callback, NULL);
-    cti_get_tunnel_name(client->server_state, client, dnssd_client_get_tunnel_name_callback, NULL);
+    cti_track_active_data_set(client->server_state, &client->active_data_set_connection,
+                              client, dnssd_client_active_data_set_changed_callback, NULL);
     RETAIN_HERE(client, dnssd_client); // for callback
+    dnssd_client_active_data_set_changed_callback(client, kCTIStatus_NoError); // Get the initial state.
     state_machine_next_state(&client->state_header, dnssd_client_state_startup);
 }
 
diff --git a/ServiceRegistration/dnssd-client.h b/ServiceRegistration/dnssd-client.h
index 867e386..4bd31dd 100644
--- a/ServiceRegistration/dnssd-client.h
+++ b/ServiceRegistration/dnssd-client.h
@@ -20,6 +20,8 @@
 #ifndef __DNSSD_CLIENT_H__
 typedef struct dnssd_client dnssd_client_t;
 RELEASE_RETAIN_DECLS(dnssd_client);
+#define dnssd_client_retain(watcher) dnssd_client_retain_(watcher, __FILE__, __LINE__)
+#define dnssd_client_release(watcher) dnssd_client_release_(watcher, __FILE__, __LINE__)
 void dnssd_client_cancel(dnssd_client_t *NONNULL client);
 dnssd_client_t *NULLABLE dnssd_client_create(srp_server_t *NONNULL server_state);
 void dnssd_client_start(dnssd_client_t *NONNULL client);
diff --git a/ServiceRegistration/dnssd-proxy.c b/ServiceRegistration/dnssd-proxy.c
index da1d6d3..75c0e1b 100644
--- a/ServiceRegistration/dnssd-proxy.c
+++ b/ServiceRegistration/dnssd-proxy.c
@@ -1,6 +1,6 @@
 /* dnssd-proxy.c
  *
- * Copyright (c) 2018-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2018-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -47,6 +47,7 @@
 #include <ifaddrs.h>
 #include <net/if.h>
 #include <stdarg.h>
+#include <notify.h>
 #ifdef IOLOOP_MACOS
 #include <AssertMacros.h>
 #include <SystemConfiguration/SystemConfiguration.h>
@@ -69,14 +70,22 @@
 #include "cti-services.h"
 #include "route.h"
 #include "srp-replication.h"
+#if THREAD_DEVICE
+#  include "state-machine.h"
+#  include "service-publisher.h"
+#endif
 #if SRP_FEATURE_NAT64
 #include "dns_sd_private.h"
 #include "nat64-macos.h"
 #endif
+#include "advertising_proxy_services.h"
+#include "srp-dnssd.h"
 
-#define RESPONSE_WINDOW 6 // in seconds.
+#define RESPONSE_WINDOW_MSECS 800
+#define RESPONSE_WINDOW_USECS (RESPONSE_WINDOW_MSECS * 1000) // 800ms in microseconds.
 
 extern srp_server_t *srp_servers;
+#define SERIAL(x) ((x) == NULL ? 0 : (x)->serial)
 
 // When do we build dnssd-proxy?
 // 1. When we are integrating dnssd-proxy into srp-mdns-proxy: SRP_FEATURE_COMBINED_SRP_DNSSD_PROXY == 1
@@ -161,9 +170,11 @@
     dp_tracker_session_srpl
 } dp_tracker_session_type_t;
 
+int cur_tracker_serial;
 typedef struct dnssd_query dnssd_query_t;
 typedef struct dp_tracker {
     int ref_count;
+    int serial;
     comm_t *connection;
     dnssd_query_t *dns_queries;
     dso_state_t *dso;
@@ -182,6 +193,7 @@
     uint16_t rdlen;                 // Length of resource record data returned in callback.
 };
 
+static int cur_question_serial;
 struct question {
     question_t *next;               // List of questions that are being asked.
     served_domain_t *served_domain;
@@ -192,14 +204,17 @@
     int64_t start_time;             // When this question was started.
     int serviceFlags;               // Service flags to use with this question.
     int ref_count;                  // Reference count.
+    int serial;
     uint32_t interface_index;       // Which interface the query should use.
     uint16_t type;                  // The type.
     uint16_t qclass;                // The class.
     bool no_data;                   // True if "no such record" is received or all data gets removed
 };
 
+static int cur_query_serial;
 struct dnssd_query {
     int ref_count;
+    int serial;
     dp_tracker_t *tracker;          // Tracks the connection that delivered this query.
     dnssd_query_t *next;            // For DNS queries, tracks other queries on the same connection, if any.
     wakeup_t *wakeup;
@@ -216,8 +231,9 @@
     dns_message_t *response_msg;    // In case we need to decompose the message to construct a multi-answer message.
     size_t data_size;               // Size of the data payload of the response.
     dnssd_query_t *question_next;   // Linked list of queries on the question this query is subscribed to.
-    question_t *question;     // Question asked by this query pointing to a cache entry.
+    question_t *question;           // Question asked by this query pointing to a cache entry.
     bool satisfied;                 // If true, this query has gotten an answer. Only relevant for straight DNS.
+    bool canceled;                  // If true, dnssd_query_cancel has been called on this query, so don't cancel it again.
 };
 
 // Structure that is used to setup the mDNS discovery for dnssd-proxy.
@@ -232,11 +248,21 @@
     SCDynamicStoreContext sc_context;
 };
 
+#if SRP_FEATURE_DISCOVERY_PROXY_SERVER
+// Structure that is used to advertise the push discovery service in .local domain.
+struct dnssd_dp_proxy_advertisements {
+    wakeup_t *wakeup_timer;     // Used to setup a timer to advertise push service repeatedly until it succeeds.
+    dnssd_txn_t *txn;           // The event loop.
+    DNSServiceRef service_ref;  // DNSServiceRef to register the service.
+    srp_server_t *server_state;
+};
+#endif
+
 // Configuration file settings
 
-uint16_t udp_port;
-uint16_t tcp_port;
-uint16_t tls_port;
+uint16_t dnssd_proxy_udp_port;
+uint16_t dnssd_proxy_tcp_port;
+uint16_t dnssd_proxy_tls_port;
 const char *my_name = "discoveryproxy.home.arpa.";
 char *listen_addrs[MAX_ADDRS];
 int num_listen_addrs;
@@ -246,12 +272,14 @@
 char *tls_cert_filename = "/etc/dnssd-proxy/server.crt";
 char *tls_key_filename = "/etc/dnssd-proxy/server.key";
 
-comm_t *listener[4 + MAX_ADDRS];
-int num_listeners;
+comm_t *dnssd_proxy_listeners[4 + MAX_ADDRS];
+int dnssd_proxy_num_listeners;
 question_t *questions_without_domain; // Questions that aren't in a served domain
 served_domain_t *served_domains;
 int num_push_sessions; // Number of connections from DNS Push clients
 int dp_num_outstanding_queries;
+int num_push_sessions_dropped_for_load;
+int num_queries_dropped_for_load;
 
 #if SRP_FEATURE_DNSSD_PROXY_SHARED_CONNECTIONS
 dnssd_txn_t *shared_discovery_txn;
@@ -259,11 +287,12 @@
 wakeup_t *discovery_restart_wakeup;
 
 #if SRP_FEATURE_DYNAMIC_CONFIGURATION
-static CFStringRef sc_dynamic_store_key_host_name;
+static char uuid_name[DNS_MAX_NAME_SIZE + 1];
 static char my_name_buf[DNS_MAX_NAME_SIZE + 1];
-#endif // #if (SRP_FEATURE_COMBINED_SRP_DNSSD_PROXY)
+static CFStringRef sc_dynamic_store_key_host_name;
 static char local_host_name[DNS_MAX_NAME_SIZE + 1];
 static char local_host_name_dot_local[DNS_MAX_NAME_SIZE + 1];
+#endif // #if (SRP_FEATURE_COMBINED_SRP_DNSSD_PROXY)
 
 #if THREAD_BORDER_ROUTER && SRP_FEATURE_SRP_COMBINED_DNSSD_PROXY
 extern char *thread_interface_name;
@@ -279,14 +308,13 @@
 
 // Macros
 
-#define LOCAL_ONLY_PSEUDO_INTERFACE "local only pseudo interface"
-#define ALL_LOCALS_PSEUDO_INTERFACE "all locally-discoverable services pseudo interface"
 #define THREAD_DOMAIN "thread.home.arpa."
 // "openthread." will change in the future once we have a way to get thread network ID
 #define THREAD_DOMAIN_WITH_ID "openthread." THREAD_DOMAIN
 #define DEFAULT_SERVICE_ARPA_DOMAIN "default.service.arpa."
 #define HOME_NET_DOMAIN "home.arpa."
 #define DOT_HOME_NET_DOMAIN ".home.arpa."
+#define DOT_LOCAL_DOMAIN "local."
 #define LOCAL "local."
 #define DOT_LOCAL ".local."
 #define IPV4_REVERSE_LOOKUP_DOMAIN "in-addr.arpa."
@@ -301,28 +329,42 @@
 // interval of <10s, which is kind of painful.
 #define RFC8766_TTL_CLAMP 300
 
-#define VALIDATE_TRACKER_CONNECTION_NON_NULL()                      \
-    do {                                                            \
-        if (query->tracker == NULL) {                               \
-            ERROR("query->tracker unexpectedly NULL!");             \
-            return;                                                 \
-        }                                                           \
-        if (query->tracker->connection == NULL) {                   \
-            ERROR("query->tracker->connection unexpectedly NULL!"); \
-            return;                                                 \
-        }                                                           \
+#define VALIDATE_TRACKER_CONNECTION_NON_NULL()                                                            \
+    do {                                                                                                  \
+        if (query->tracker == NULL) {                                                                     \
+            ERROR("[Q%d] query->tracker NULL for query!", SERIAL(query));                                 \
+            return;                                                                                       \
+        }                                                                                                 \
+        if (query->tracker->connection == NULL) {                                                         \
+            ERROR("[Q%d][TRK%d] query->tracker->connection NULL", SERIAL(query), SERIAL(query->tracker)); \
+            return;                                                                                       \
+        }                                                                                                 \
     } while (false)
 
+#ifdef SRP_TEST_SERVER
+extern void (*srp_test_dso_message_finished)(void *context, message_t *message, dso_state_t *dso);
+extern ready_callback_t srp_test_dnssd_tls_listener_ready;
+extern void *srp_test_tls_listener_context;
+#endif
+
 // Forward references
 
+static void dns_push_start(dnssd_query_t *query);
+
+#if SRP_FEATURE_DYNAMIC_CONFIGURATION
+static void served_domain_free(served_domain_t *const served_domain);
+#endif
+
 static served_domain_t *NULLABLE
 new_served_domain(dp_interface_t *const NULLABLE interface, const char * NONNULL domain);
 
+#if STUB_ROUTER
 static served_domain_t *NULLABLE
 find_served_domain(const char *const NONNULL domain);
 
 static bool
 string_ends_with(const char *const NONNULL str, const char *const NONNULL suffix);
+#endif // STUB_ROUTER
 
 static void dp_query_towire_reset(dnssd_query_t *query);
 
@@ -331,17 +373,21 @@
 add_new_served_domain_with_interface(const char *const NONNULL name,
     const addr_t *const NULLABLE address, const addr_t *const NULLABLE mask);;
 
+#if STUB_ROUTER
 static bool
 dnssd_hardwired_add_or_remove_address_in_domain(const char *const NONNULL name,
     const char *const NONNULL domain_to_change, const addr_t *const NONNULL address, const bool add);
+#endif // STUB_ROUTER
 
 static bool
 dnssd_hardwired_setup_dns_push_for_domain(served_domain_t *const NONNULL served_domain);
 #endif // SRP_FEATURE_DYNAMIC_CONFIGURATION
 
+#if STUB_ROUTER
 static bool
 start_timer_to_advertise(dnssd_proxy_advertisements_t *NONNULL context,
     const char *const NULLABLE domain_to_advertise, const uint32_t interval);
+#endif
 
 static bool
 interface_process_addr_change(dp_interface_t *const NONNULL interface, const addr_t *const NONNULL address,
@@ -354,13 +400,18 @@
 #if SRP_FEATURE_DNSSD_PROXY_SHARED_CONNECTIONS
 static void dp_setup_shared_discovery_txn(void);
 #endif
+static void dp_query_reply_from_cache(question_t *question, dnssd_query_t *query, bool remove);
 static void dp_handle_server_disconnect(void *UNUSED context, int status);
+static void dp_question_answers_free(question_t *question);
 static void question_finalize(question_t *question);
 
 // For debugging
 static wakeup_t *connection_dropper;
 extern dso_state_t *dso_connections;
 static void dp_drop_connections(void *UNUSED context);
+#ifdef SRP_TEST_SERVER
+served_domain_t *NULLABLE last_freed_domain;
+#endif
 
 static void
 dp_question_context_release(void *context)
@@ -384,7 +435,7 @@
         len = strlen(question->name);
         if (question->served_domain->interface != NULL) {
             if (len + sizeof local_suffix > sizeof name) {
-                ERROR("question name %s is too long for .local.", name);
+                ERROR("[QU%d] question name %s is too long for .local.", SERIAL(question), name);
                 return kDNSServiceErr_BadParam;
             }
             memcpy(name, question->name, len);
@@ -392,7 +443,7 @@
         } else {
             size_t dlen = strlen(question->served_domain->domain_ld) + 1;
             if (len + dlen > sizeof name) {
-                ERROR("question name %s is too long for %s.", name, question->served_domain->domain);
+                ERROR("[QU%d] question name %s is too long for %s.", SERIAL(question), name, question->served_domain->domain);
                 return kDNSServiceErr_BadParam;
             }
             memcpy(name, question->name, len);
@@ -412,33 +463,59 @@
     }
 #endif // SRP_FEATURE_DNSSD_PROXY_SHARED_CONNECTIONS
 
+    uint32_t question_interface = question->interface_index;
+#if SRP_FEATURE_LOCAL_DISCOVERY
+    if (question_interface == kDNSServiceInterfaceIndexInfra) {
+        int ret = -1;
+#if STUB_ROUTER
+        ret = route_get_current_infra_interface_index();
+#else
+        static unsigned en0_ifindex = 0;
+        if (en0_ifindex == 0) {
+            en0_ifindex = if_nametoindex("en0");
+            if (en0_ifindex == 0) {
+                ERROR("getting en0 ifindex failed!");
+            }
+        }
+        if (en0_ifindex != 0) {
+            ret = en0_ifindex;
+        }
+#endif // STUB_ROUTER
+        // If we don't have an infrastructure interface, refuse the query.
+        if (ret < 0) {
+            return kDNSServiceErr_Refused;
+        }
+        question_interface = ret;
+    }
+#endif // SRP_FEATURE_LOCAL_DISCOVERY
+
 #if SRP_FEATURE_NAT64
     const DNSServiceAttribute *attr = NULL;
     if (dns64 && (question->type == dns_rrtype_aaaa) && (question->qclass == dns_qclass_in)) {
         attr = &kDNSServiceAttributeAAAAFallback;
     }
-    err = DNSServiceQueryRecordWithAttribute(&sdref, question->serviceFlags | shared_connection_flag,
-                                             question->interface_index, np, question->type,
-                                             question->qclass, attr, dns_question_callback, question);
+    err = dns_service_query_record_wa(srp_servers, &sdref, question->serviceFlags | shared_connection_flag,
+                                      question_interface, np, question->type,
+                                      question->qclass, attr, dns_question_callback, question);
 #else
     (void)dns64;
-    err = DNSServiceQueryRecord(&sdref, question->serviceFlags | shared_connection_flag, question->interface_index, np,
-                                question->type, question->qclass, dns_question_callback, question);
+    err = dns_service_query_record(srp_servers, &sdref, question->serviceFlags | shared_connection_flag, question_interface, np,
+                                   question->type, question->qclass, dns_question_callback, question);
 #endif
     if (err != kDNSServiceErr_NoError) {
-        ERROR("DNSServiceQueryRecord failed for '%s': %d", np, err);
+        ERROR("[QU%d] DNSServiceQueryRecord failed for '%s': %d", SERIAL(question), np, err);
     } else {
-        INFO("txn %p new sdref %p", question->txn, sdref);
+        INFO("[QU%d] txn %p new sdref %p", SERIAL(question), question->txn, sdref);
 #if SRP_FEATURE_DNSSD_PROXY_SHARED_CONNECTION
         question->txn = ioloop_dnssd_txn_add_subordinate(sdref, dp_question_context_release, NULL);
 #else
-        question->txn = ioloop_dnssd_txn_add(sdref, question, dp_question_context_release, dp_handle_server_disconnect);
+        question->txn = dns_service_ioloop_txn_add(srp_servers, sdref, question, dp_question_context_release, dp_handle_server_disconnect);
 #endif // SRP_FEATURE_DNSSD_PROXY_SHARED_CONNECTIONS
         RETAIN_HERE(question, question); // For the callback
 #if SRP_FEATURE_NAT64
-        INFO("DNSServiceQueryRecordWithAttribute started for '" PRI_S_SRP "': %d", np, err);
+        INFO("[QU%d] DNSServiceQueryRecordWithAttribute started for '" PRI_S_SRP "': %d", SERIAL(question), np, err);
 #else
-        INFO("DNSServiceQueryRecord started for '" PRI_S_SRP "': %d", np, err);
+        INFO("[QU%d] DNSServiceQueryRecord started for '" PRI_S_SRP "': %d", SERIAL(question), np, err);
 #endif // SRP_FEATURE_NAT64
     }
     return err;
@@ -496,11 +573,27 @@
 dp_void_question(question_t *question, void *UNUSED context)
 {
     if (question->txn != NULL) {
-        INFO("question->txn = %p", question->txn);
+        INFO("[QU%d] question->txn = %p", SERIAL(question), question->txn);
+#if SRP_FEATURE_DNSSD_PROXY_SHARED_CONNECTIONS
         question->txn->sdref = NULL;
+#else
+        ioloop_dnssd_txn_cancel(question->txn);
+#endif
         ioloop_dnssd_txn_release(question->txn);
         question->txn = NULL;
     }
+    if (question->answers != NULL) {
+        dnssd_query_t *next, *query = question->queries;
+        while(query != NULL) {
+            next = query->question_next;
+            if (query->dso != NULL) {
+                dp_query_reply_from_cache(question, query, true);
+            }
+            query = next;
+        }
+        dp_question_answers_free(question);
+    }
+
     return false;
 }
 
@@ -592,7 +685,7 @@
 }
 
 static void
-answer_free(answer_t *answer)
+dp_answer_free(answer_t *answer)
 {
     if (answer != NULL) {
         free(answer->fullname);
@@ -600,22 +693,27 @@
     }
 }
 
-// The finalize function will deallocate answers associated with the question,
-// remove question from the question list and deallocate the question.
 static void
-question_finalize(question_t *question)
+dp_question_answers_free(question_t *question)
 {
-    INFO("type %d class %d " PRI_S_SRP,
-         question->type, question->qclass, question->name);
     // De-allocate answers
     answer_t *answer = question->answers;
     answer_t *next;
     while (answer != NULL) {
         next = answer->next;
-        answer_free(answer);
+        dp_answer_free(answer);
         answer = next;
     }
     question->answers = NULL;
+}
+
+// The finalize function will deallocate answers associated with the question,
+// remove question from the question list and deallocate the question.
+static void
+question_finalize(question_t *question)
+{
+    INFO("[QU%d] type %d class %d " PRI_S_SRP, SERIAL(question), question->type, question->qclass, question->name);
+    dp_question_answers_free(question);
     free(question->name);
     free(question);
 }
@@ -624,7 +722,7 @@
 dp_question_cancel(question_t *question)
 {
     if (question->txn != NULL) {
-        INFO("question->txn = %p sdref=%p", question->txn, question->txn->sdref);
+        INFO("[QU%d] question->txn = %p sdref=%p", SERIAL(question), question->txn, question->txn->sdref);
         ioloop_dnssd_txn_cancel(question->txn);
         ioloop_dnssd_txn_release(question->txn);
         question->txn = NULL;
@@ -646,6 +744,19 @@
             questions = &q_cur->next;
         }
     }
+    // If this was the last question, see if the served domain is still on the served domain list; if not,
+    // this is the last reference, so free it.
+    if (question->served_domain != NULL && question->served_domain->questions == NULL) {
+        served_domain_t *served_domain;
+        for (served_domain = served_domains; served_domain; served_domain = served_domain->next) {
+            if (served_domain == question->served_domain) {
+                break;
+            }
+        }
+        if (served_domain == NULL) {
+            served_domain_free(question->served_domain);
+        }
+    }
     RELEASE_HERE(question, question); // Release from the list.
 }
 
@@ -726,7 +837,7 @@
             FAULT("DNS Push connection count went negative");
             num_push_sessions = 0;
         } else {
-            INFO("dso connection count dropped: %d", num_push_sessions);
+            INFO("[TRK%d][DSO%d][C%d] dso connection count dropped: %d", SERIAL(tracker), SERIAL(tracker->dso), SERIAL(tracker->connection), num_push_sessions);
         }
     }
 }
@@ -738,7 +849,8 @@
     // Shouldn't be NULL.
     if (tracker->connection != NULL) {
         comm_t *connection = tracker->connection;
-        INFO("tracker for connection " PRI_S_SRP " has gone idle.", tracker->connection->name);
+        INFO("[TRK%d][DSO%d][C%d] tracker for connection " PRI_S_SRP " has gone idle.",
+             SERIAL(tracker), SERIAL(tracker->dso), SERIAL(connection), connection->name);
 
         // If the connection is already disconnected, it's already released its reference to the tracker. If not,
         // the release below will release tracker as a side effect. So in case tracker survives, clear the
@@ -764,9 +876,10 @@
             tracker->idle_timeout = ioloop_wakeup_create();
         }
         if (tracker->idle_timeout == NULL) {
-            ERROR("no memory for idle timeout");
+            ERROR("[TRK%d] no memory for idle timeout", SERIAL(tracker));
         } else {
-            ioloop_add_wake_event(tracker->idle_timeout, tracker, dp_tracker_idle, NULL, seconds * MSEC_PER_SEC);
+            ioloop_add_wake_event(tracker->idle_timeout, tracker, dp_tracker_idle, dp_tracker_context_release, seconds * MSEC_PER_SEC);
+            RETAIN_HERE(tracker, dp_tracker);
         }
     }
 }
@@ -786,13 +899,21 @@
 static void
 dnssd_query_cancel(dnssd_query_t *query)
 {
-    INFO(PRI_S_SRP PUB_S_SRP,
+    INFO("[Q%d][QU%d] " PRI_S_SRP PUB_S_SRP PUB_S_SRP, SERIAL(query), SERIAL(query->question),
          query->question == NULL ? "<null>" : query->question->name,
          query->question == NULL ? "" : ((query->question->served_domain
-         ? (query->question->served_domain->interface ? DOT_LOCAL : query->question->served_domain->domain_ld)
-         : "")));
-    // Retain the query for the duration of dnssd_query_cancel so that it doesn't get released while we are working on it.
+                                          ? (query->question->served_domain->interface
+                                             ? DOT_LOCAL
+                                             : query->question->served_domain->domain_ld)
+                                          : "")),
+         query->canceled ? " canceled" : "");
+    // Avoid double-cancellation
+    if (query->canceled) {
+        return;
+    }
+    // Retain the query for the duration of dnssd_query_cancel so that it doesn't get finalized while we are working on it.
     RETAIN_HERE(query, dnssd_query);
+    query->canceled = true; // prevent double-cancellation
     if (query->tracker != NULL) {
         dp_tracker_t *tracker = query->tracker;
 
@@ -842,8 +963,8 @@
             if (query->activity != NULL && query->dso != NULL) {
                 dso_activity_t *activity = query->activity;
                 dso_state_t *dso = query->dso;
-                query->activity = NULL;
                 dso_drop_activity(dso, activity);
+                query->activity = NULL;
             }
             // Now release the reference the query had on the tracker.
             query->tracker = NULL;
@@ -883,7 +1004,7 @@
 
     while (*qp != NULL) {
         if (*qp == query) {
-            ERROR("query is already being tracked.");
+            ERROR("[Q%d][TRK%d] query is already being tracked.", SERIAL(query), SERIAL(query->tracker));
             return;
         }
         qp = &(*qp)->next;
@@ -901,7 +1022,7 @@
     tracker->connection = NULL;
     tracker->dns_queries = NULL;
 
-    INFO("tracker %p queries %p dso %p comm %p", tracker, dns_queries, tracker->dso, tracker_connection);
+    INFO("[TRK%d][DSO%d][C%d] queries %p", SERIAL(tracker), SERIAL(tracker->dso), SERIAL(tracker_connection), dns_queries);
 
     // If there is a DSO state outstanding on the tracker, cancel any activities connected to it.
     if (tracker->dso != NULL) {
@@ -924,6 +1045,7 @@
     // in turn finalize the tracker.
     if (tracker_connection != NULL) {
         ioloop_comm_release(tracker_connection);
+        tracker->connection = NULL;
     }
 
     // If dns_queries is non-null, tracker still exists, but it might go away when we cancel the last
@@ -940,7 +1062,7 @@
 dns_push_cancel(dso_activity_t *activity)
 {
     dnssd_query_t *query = (dnssd_query_t *)activity->context;
-    INFO(PUB_S_SRP, activity->name);
+    INFO("[Q%d][QU%d] " PUB_S_SRP, SERIAL(query), SERIAL(query->question), activity->name);
     // We can either get here because the dso object is being finalized, or because the activity is being dropped.
     // In the former case, we need to cancel the query. In the latter case, we've been called as a result of
     // dnssd_query_cancel calling dso_drop_activity. dnssd_query_cancel sets query->activity to NULL before dropping
@@ -1030,7 +1152,8 @@
 
 static bool
 dp_query_add_data_to_response(dnssd_query_t *query, const char *fullname, uint16_t rrtype, uint16_t rrclass,
-                              uint16_t rdlen, const void *rdata, int32_t ttl, const bool hardwired_response)
+                              uint16_t rdlen, const void *rdata, int32_t ttl, const bool hardwired_response,
+                              bool dont_elide, uint16_t *counter)
 {
     bool record_added;
     dns_towire_state_t *towire = &query->towire;
@@ -1039,32 +1162,35 @@
     char pbuf[DNS_MAX_NAME_SIZE + 1];
     char rbuf[DNS_MAX_NAME_SIZE + 1];
     uint8_t *revert = query->towire.p; // Remember where we were in case there's no room.
+    question_t *question = query->question;
 
     // Only do the translation if:
     // 1. We serve the domain.
     // 2. The response we will add does not come from our hardwired response set.
-    const bool translate = (query->question->served_domain != NULL) && (!hardwired_response);
+    const bool translate = (question->served_domain != NULL) && (!hardwired_response);
 
     if (rdlen == 0) {
-        INFO("Eliding zero-length response for " PRI_S_SRP " %d %d", fullname, rrtype, rrclass);
+        INFO("[Q%d][QU%d] eliding zero-length response for " PRI_S_SRP " " PUB_S_SRP " %d",
+             SERIAL(query), SERIAL(question), fullname, dns_rrtype_to_string(rrtype), rrclass);
         record_added = false;
         goto exit;
     }
     // Don't send A records for 127.* nor AAAA records for ::1
-    if (rrtype == dns_rrtype_a && rdlen == 4) {
+    if (dont_elide) {
+    } else if (rrtype == dns_rrtype_a && rdlen == 4) {
         // Should use IN_LINKLOCAL and IN_LOOPBACK macros here, but for some reason they are not present on
         // OpenWRT.
         if (rd[0] == 127) {
             IPv4_ADDR_GEN_SRP(rd, rd_buf);
-            INFO("Eliding localhost response for " PRI_S_SRP ": " PRI_IPv4_ADDR_SRP, fullname,
-                  IPv4_ADDR_PARAM_SRP(rd, rd_buf));
+            INFO("[Q%d][QU%d] eliding localhost response for " PRI_S_SRP ": " PRI_IPv4_ADDR_SRP,
+                 SERIAL(query), SERIAL(question), fullname, IPv4_ADDR_PARAM_SRP(rd, rd_buf));
             record_added = false;
             goto exit;
         }
         if (rd[0] == 169 && rd[1] == 254) {
             IPv4_ADDR_GEN_SRP(rd, rd_buf);
-            INFO("Eliding link-local response for " PRI_S_SRP ": " PRI_IPv4_ADDR_SRP, fullname,
-                 IPv4_ADDR_PARAM_SRP(rd, rd_buf));
+            INFO("[Q%d][QU%d] eliding link-local response for " PRI_S_SRP ": " PRI_IPv4_ADDR_SRP,
+                 SERIAL(query), SERIAL(question), fullname, IPv4_ADDR_PARAM_SRP(rd, rd_buf));
             record_added = false;
             goto exit;
         }
@@ -1072,31 +1198,34 @@
         struct in6_addr addr = *(struct in6_addr *)rdata;
         if (IN6_IS_ADDR_LOOPBACK(&addr)) {
             SEGMENTED_IPv6_ADDR_GEN_SRP(rdata, rdata_buf);
-            INFO("Eliding localhost response for " PRI_S_SRP ": " PRI_SEGMENTED_IPv6_ADDR_SRP,
-                 fullname, SEGMENTED_IPv6_ADDR_PARAM_SRP(rdata, rdata_buf));
+            INFO("[Q%d][QU%d] eliding localhost response for " PRI_S_SRP ": " PRI_SEGMENTED_IPv6_ADDR_SRP,
+                 SERIAL(query), SERIAL(question), fullname, SEGMENTED_IPv6_ADDR_PARAM_SRP(rdata, rdata_buf));
             record_added = false;
             goto exit;
         }
         if (IN6_IS_ADDR_LINKLOCAL(&addr)) {
             SEGMENTED_IPv6_ADDR_GEN_SRP(rdata, rdata_buf);
-            INFO("Eliding link-local response for " PRI_S_SRP ": " PRI_SEGMENTED_IPv6_ADDR_SRP,
-                 fullname, SEGMENTED_IPv6_ADDR_PARAM_SRP(rdata, rdata_buf));
+            INFO("[Q%d][QU%d] eliding link-local response for " PRI_S_SRP ": " PRI_SEGMENTED_IPv6_ADDR_SRP,
+                 SERIAL(query), SERIAL(question), fullname, SEGMENTED_IPv6_ADDR_PARAM_SRP(rdata, rdata_buf));
             record_added = false;
             goto exit;
         }
     }
-    INFO("survived for rrtype %d rdlen %d", rrtype, rdlen);
+    INFO("survived for rrtype " PUB_S_SRP " rdlen %d", dns_rrtype_to_string(rrtype), rdlen);
 
+    if (query->dso != NULL) {
+        dns_push_start(query);
+    }
     // Rewrite the domain if it's .local.
-    if (query->question->served_domain != NULL) {
+    if (question->served_domain != NULL) {
         TOWIRE_CHECK("concatenate_name_to_wire", towire,
-                     dns_concatenate_name_to_wire(towire, NULL, query->question->name, query->question->served_domain->domain));
-        INFO(PUB_S_SRP " answer:  type %02d class %02d " PRI_S_SRP "." PRI_S_SRP, query->dso != NULL ? "PUSH" : "DNS ",
-             rrtype, rrclass, query->question->name, query->question->served_domain->domain);
+                     dns_concatenate_name_to_wire(towire, NULL, question->name, question->served_domain->domain));
+        INFO("[Q%d][QU%d] " PUB_S_SRP " answer:  type " PUB_S_SRP " class %02d " PRI_S_SRP "." PRI_S_SRP, SERIAL(query), SERIAL(question),
+             query->dso != NULL ? "PUSH" : "DNS ", dns_rrtype_to_string(rrtype), rrclass, question->name, question->served_domain->domain);
     } else {
-        TOWIRE_CHECK("compress_name_to_wire", towire, dns_concatenate_name_to_wire(towire, NULL, NULL, query->question->name));
-        INFO(PUB_S_SRP " answer:  type %02d class %02d " PRI_S_SRP " (p)",
-             query->dso != NULL ? "push" : " dns", rrtype, rrclass, query->question->name);
+        TOWIRE_CHECK("compress_name_to_wire", towire, dns_concatenate_name_to_wire(towire, NULL, NULL, question->name));
+        INFO("[Q%d][QU%d] " PUB_S_SRP " answer:  type " PUB_S_SRP " class %02d " PRI_S_SRP " (p)",
+             SERIAL(query), SERIAL(question), query->dso != NULL ? "push" : " dns", dns_rrtype_to_string(rrtype), rrclass, question->name);
     }
     TOWIRE_CHECK("rrtype", towire, dns_u16_to_wire(towire, rrtype));
     TOWIRE_CHECK("rrclass", towire, dns_u16_to_wire(towire, rrclass));
@@ -1132,7 +1261,7 @@
                 TOWIRE_CHECK("answer.data.srv.port", towire, dns_u16_to_wire(towire, answer.data.srv.port));
                 break;
             default:
-                INFO("record type %d not translated", rrtype);
+                INFO("[Q%d][QU%d] record type " PUB_S_SRP " not translated", SERIAL(query), SERIAL(question), dns_rrtype_to_string(rrtype));
                 dns_rrdata_free(&answer);
                 goto raw;
         }
@@ -1145,31 +1274,35 @@
             truncate_local(name);
             dns_name_print(name, pbuf, sizeof pbuf);
             TOWIRE_CHECK("concatenate_name_to_wire 2", towire,
-                         dns_concatenate_name_to_wire(towire, name, NULL, query->question->served_domain->domain));
-            INFO("translating " PRI_S_SRP " to " PRI_S_SRP " . " PRI_S_SRP, rbuf, pbuf, query->question->served_domain->domain);
+                         dns_concatenate_name_to_wire(towire, name, NULL, question->served_domain->domain));
+            INFO("[Q%d][QU%d] translating " PRI_S_SRP " to " PRI_S_SRP " . " PRI_S_SRP,
+                 SERIAL(query), SERIAL(question), rbuf, pbuf, question->served_domain->domain);
         } else {
             TOWIRE_CHECK("concatenate_name_to_wire 2", towire,
                          dns_concatenate_name_to_wire(towire, name, NULL, NULL));
-            INFO("compressing " PRI_S_SRP, rbuf);
+            INFO("[Q%d][QU%d] compressing " PRI_S_SRP, SERIAL(query), SERIAL(question), rbuf);
         }
 
         dns_name_free(name);
         dns_rdlength_end(towire);
     } else {
-        ERROR("dp_query_add_data_to_response: rdata from mDNSResponder didn't parse!!");
+        ERROR("[Q%d][QU%d] rdata from mDNSResponder didn't parse!!", SERIAL(query), SERIAL(question));
     raw:
         TOWIRE_CHECK("rdlen", towire, dns_u16_to_wire(towire, rdlen));
         TOWIRE_CHECK("rdata", towire, dns_rdata_raw_data_to_wire(towire, rdata, rdlen));
     }
 
     if (towire->truncated || failnote) {
-        ERROR("RR ADD FAIL: dp_query_add_data_to_response: " PUB_S_SRP, failnote);
+        ERROR("[Q%d][QU%d] RR ADD FAIL: " PUB_S_SRP, SERIAL(query), SERIAL(question), failnote);
         query->towire.p = revert;
         record_added = false;
         goto exit;
     }
 
     record_added = true;
+    if (counter != NULL && query->dso == NULL) {
+        *counter = htons(ntohs(*counter) + 1);
+    }
 exit:
     return record_added;
 }
@@ -1265,6 +1398,7 @@
          hp->fullname, hp->name, hp->type, hp->rdlen);
 }
 
+#if STUB_ROUTER
 static bool
 dnssd_hardwired_remove_record(served_domain_t *const NONNULL sdt, const char *const NONNULL name, const char *const NONNULL domain, size_t rdlen,
     const void *const NULLABLE rdata, uint16_t type)
@@ -1557,6 +1691,15 @@
         goto exit;
     }
 
+#if SRP_FEATURE_LOCAL_DISCOVERY
+    // Update the default.service.arpa. address mapping.
+    succeeded = dnssd_hardwired_add_or_remove_address_in_domain(local_host_name, DOT_LOCAL_DOMAIN, addr, add);
+    if (!succeeded) {
+        ERROR("failed to update address record for domain - domain: " PUB_S_SRP, LOCAL);
+        goto exit;
+    }
+#endif
+
     // Setup the "_lb.dns-sd"
     // Update the "reverse mapping from address to browsing domain" for each eligible served domain under IPv6 or IPv4
     // reverse lookup domain.
@@ -1618,6 +1761,7 @@
 exit:
     return;
 }
+#endif
 
 static void
 dnssd_hardwired_setup(void)
@@ -1625,11 +1769,9 @@
     dns_wire_t wire;
     dns_towire_state_t towire;
     served_domain_t *sdt;
-    int i;
+#if STUB_ROUTER
     dns_name_t *my_name_parsed = my_name == NULL ? NULL : dns_pres_name_parse(my_name);
-    char namebuf[DNS_MAX_NAME_SIZE + 1];
-    const char *local_name;
-    addr_t addr;
+#endif
 
 #define RESET \
     memset(&towire, 0, sizeof towire); \
@@ -1662,6 +1804,11 @@
         // overwritten immediately; otherwise it will be overwritten when the TLS key has been generated and signed.
         dnssd_hardwired_add(sdt, "_dns-push-tls._tcp", sdt->domain_ld, towire.p - wire.data, wire.data, dns_rrtype_srv);
 
+#if STUB_ROUTER
+        char namebuf[DNS_MAX_NAME_SIZE + 1];
+        const char *local_name;
+        addr_t addr;
+
         // If my_name wasn't set, or if my_name is in this interface's domain, we need to answer
         // for it when queried.
         if (my_name == NULL || my_name_parsed != NULL) {
@@ -1687,7 +1834,7 @@
                 }
             }
             if (local_name != NULL) {
-                for (i = 0; i < num_publish_addrs; i++) {
+                for (int i = 0; i < num_publish_addrs; i++) {
                     RESET;
                     memset(&addr, 0, sizeof addr);
                     getipaddr(&addr, publish_addrs[i]);
@@ -1707,16 +1854,22 @@
                 }
             }
         }
+#endif // STUB_ROUTER
 
         // NS
         RESET;
+#if STUB_ROUTER
         if (string_ends_with(sdt->domain, THREAD_DOMAIN)) {
             // For served domain in the THREAD_DOMAIN, set the NS record to the local host name:
             // For example, openthread.thread.home.arpa. NS Office.local.
+            // XXX is this right?
             require_quiet(local_host_name_dot_local[0] != 0, exit);
             dns_full_name_to_wire(NULL, &towire, local_host_name_dot_local);
-        } else if (my_name != NULL) {
-            dns_full_name_to_wire(NULL, &towire, my_name);
+        } else
+#endif
+        if (uuid_name[0] != 0) {
+            dns_name_to_wire(NULL, &towire, uuid_name);
+            dns_full_name_to_wire(NULL, &towire, sdt->domain);
         } else {
             dns_name_to_wire(NULL, &towire, "ns");
             dns_full_name_to_wire(NULL, &towire, sdt->domain);
@@ -1736,6 +1889,7 @@
 
     // Setup hardwired response A/AAAA record for <local host name>.home.arpa.
 #if SRP_FEATURE_COMBINED_SRP_DNSSD_PROXY
+#if STUB_ROUTER
     // When dnssd-proxy is combined with srp-mdns-proxy, we get the address from the interface address list not from the
     // config file, so we search through the served domains for all available address.
     if (my_name_parsed != NULL) {
@@ -1752,10 +1906,15 @@
     require_action(thread_served_domain != NULL, exit,
         ERROR("Failed to find thread domain - domain: " PUB_S_SRP, THREAD_DOMAIN_WITH_ID));
 
-    served_domain_t *const default_service_arpa_domain = find_served_domain(THREAD_DOMAIN_WITH_ID);
+    served_domain_t *const default_service_arpa_domain = find_served_domain(DEFAULT_SERVICE_ARPA_DOMAIN);
     require_action(default_service_arpa_domain != NULL, exit,
-        ERROR("Failed to find thread domain - domain: " PUB_S_SRP, THREAD_DOMAIN_WITH_ID));
+        ERROR("Failed to find thread domain - domain: " PUB_S_SRP, DEFAULT_SERVICE_ARPA_DOMAIN));
 
+#if SRP_FEATURE_LOCAL_DISCOVERY
+    served_domain_t *const dot_local_domain = find_served_domain(DOT_LOCAL_DOMAIN);
+    require_action(dot_local_domain != NULL, exit,
+        ERROR("Failed to find thread domain - domain: " PUB_S_SRP, DOT_LOCAL_DOMAIN));
+#endif
     for (const served_domain_t *domain = served_domains; domain != NULL; domain = domain->next) {
         if (domain->interface == NULL) {
             continue;
@@ -1792,8 +1951,15 @@
             // <local host name>.default.service.arpa. A/AAAA <IP address>
             dnssd_hardwired_add(default_service_arpa_domain, local_host_name, default_service_arpa_domain->domain_ld,
                 towire.p - wire.data, wire.data, rr_type);
+
+#if SRP_FEATURE_LOCAL_DISCOVERY
+            // <local host name>.local. A/AAAA <IP address>
+            dnssd_hardwired_add(dot_local_domain, local_host_name, dot_local_domain->domain_ld,
+                towire.p - wire.data, wire.data, rr_type);
+#endif
         }
     }
+#endif
 #else // SRP_FEATURE_COMBINED_SRP_DNSSD_PROXY
     if (my_name_parsed != NULL) {
         dns_name_free(my_name_parsed);
@@ -1803,7 +1969,7 @@
         if (sdt == NULL) {
             ERROR("Unable to allocate domain for %s", my_name);
         } else {
-            for (i = 0; i < num_publish_addrs; i++) {
+            for (int i = 0; i < num_publish_addrs; i++) {
                 // AAAA
                 // A
                 RESET;
@@ -1821,11 +1987,13 @@
     }
 #endif // SRP_FEATURE_COMBINED_SRP_DNSSD_PROXY
 
+#if STUB_ROUTER
     // Setup _lb._udp.<reversed IP address> PTR record for the domain we are advertising, for example:
     // _lb._udp.0.0.168.192.in-addr.arpa. PTR my-discovery-proxy-en0.home.arpa.
     dnssd_hardwired_lbdomains_setup();
 
 exit:
+#endif
     return;
 }
 
@@ -1904,10 +2072,9 @@
 static bool
 dnssd_hardwired_setup_for_served_domain(served_domain_t *const NONNULL served_domain)
 {
-    bool succeeded;
+    bool succeeded = false;
     dns_wire_t wire;
     dns_towire_state_t towire;
-    dns_name_t *my_name_parsed = NULL;
 
 #define RESET \
     memset(&towire, 0, sizeof towire); \
@@ -1928,13 +2095,17 @@
 
     // Setup NS record for this served domain.
     RESET;
+#if STUB_ROUTER
     if (string_ends_with(served_domain->domain, THREAD_DOMAIN)) {
         // If the response requires the translation from <served domain> to ".local." and the response ends in
         // ".local.", truncate it.
         require_action_quiet(local_host_name_dot_local[0] != 0, exit, succeeded = false);
         dns_full_name_to_wire(NULL, &towire, local_host_name_dot_local);
-    } else if (my_name != NULL) {
-        dns_full_name_to_wire(NULL, &towire, my_name);
+    } else
+#endif
+    if (uuid_name[0] != 0) {
+        dns_name_to_wire(NULL, &towire, uuid_name);
+        dns_full_name_to_wire(NULL, &towire, served_domain->domain);
     } else {
         dns_name_to_wire(NULL, &towire, "ns");
         dns_full_name_to_wire(NULL, &towire, served_domain->domain);
@@ -1952,14 +2123,17 @@
     dnssd_hardwired_add(served_domain, "", served_domain->domain, towire.p - wire.data, wire.data, dns_rrtype_soa);
 
     // Setup DNS push
-    succeeded = dnssd_hardwired_setup_dns_push_for_domain(served_domain);
-    require_action_quiet(succeeded, exit,
-        ERROR("failed to setup DNS push service for hardwired response - domain: " PRI_S_SRP, served_domain->domain));
+    if (served_domain->interface == NULL || !served_domain->interface->no_push) {
+        succeeded = dnssd_hardwired_setup_dns_push_for_domain(served_domain);
+        if (!succeeded) {
+            ERROR("failed to setup DNS push service for hardwired response - domain: " PRI_S_SRP,
+                  served_domain->domain);
+            goto exit;
+        }
+    }
+    succeeded = true;
 
 exit:
-    if (my_name_parsed != NULL) {
-        dns_name_free(my_name_parsed);
-    }
     return succeeded;
 }
 
@@ -1986,16 +2160,20 @@
     dns_u16_to_wire(&towire, 0); // weight
     dns_u16_to_wire(&towire, 853); // port
 
+#if STUB_ROUTER
     if (string_ends_with(served_domain->domain, THREAD_DOMAIN)) {
         // If the served domain is subdomain of "thread.home.arpa.", use name <local host name>.local for the DNS push
         // service. Currently we only support DNS push in "thread.home.arpa." domain in local subnet, so DNS push
         // service for "thread.home.arpa." will be registered with a name in ".local.".
         require_action_quiet(local_host_name_dot_local[0] != 0, exit, succeeded = false);
         dns_full_name_to_wire(NULL, &towire, local_host_name_dot_local);
-    } else if (my_name != NULL) {
-        // Use name <local host name>.home.arpa.
-        dns_full_name_to_wire(NULL, &towire, my_name);
-    } else { // my_name == NULL
+    } else
+#endif
+    if (uuid_name[0] != 0) {
+        // Use <local host name>.<domain>
+        dns_name_to_wire(NULL, &towire, uuid_name);
+        dns_full_name_to_wire(NULL, &towire, served_domain->domain);
+    } else {
         // Use name ns.<served domain>.
         dns_name_to_wire(NULL, &towire, "ns");
         dns_full_name_to_wire(NULL, &towire, served_domain->domain);
@@ -2013,14 +2191,14 @@
 static bool
 embiggen(dnssd_query_t *query)
 {
-    dns_wire_t *nr = malloc(query->data_size + sizeof *nr); // increments wire size by DNS_DATA_SIZE
+    dns_wire_t *nr = malloc(query->data_size + DNS_DATA_SIZE + DNS_HEADER_SIZE); // increments wire size by DNS_DATA_SIZE
     if (nr == NULL) {
         return false;
     }
-    memcpy(nr, query->response, DNS_HEADER_SIZE + query->data_size);
+    memcpy(nr, query->response, query->data_size + DNS_HEADER_SIZE);
     query->data_size += DNS_DATA_SIZE;
-#define RELOCATE(x) (x) = &nr->data[0] + ((x) - &query->response->data[0])
-    RELOCATE(query->towire.p);
+    size_t len = query->towire.p - &query->response->data[0];
+    query->towire.p = &nr->data[0] + len;
     query->towire.lim = &nr->data[0] + query->data_size;
     query->towire.p_rdlength = NULL;
     query->towire.p_opt = NULL;
@@ -2214,6 +2392,7 @@
     uint16_t bitfield = ntohs(query->response->bitfield);
     uint16_t mask = 0;
     int rcode = dns_rcode_get(query->response);
+    question_t *question = query->question;
 
     // Mark this query as complete.
     query->satisfied = true;
@@ -2221,7 +2400,9 @@
     VALIDATE_TRACKER_CONNECTION_NON_NULL();
 
     // Send an SOA record if it's a .local query.
-    if (query->question->served_domain != NULL && query->question->served_domain->interface != NULL && !towire->truncated) {
+    if (question->served_domain != NULL && question->served_domain->interface != NULL &&
+        !towire->truncated && (question->type != dns_rrtype_soa || question->name[0] != '\0'))
+    {
     redo:
         // DNSSD Hybrid, Section 6.1.
         TOWIRE_CHECK("&query->enclosing_domain_pointer 1", towire,
@@ -2232,8 +2413,15 @@
                      dns_u16_to_wire(towire, dns_qclass_in));
         TOWIRE_CHECK("ttl", towire, dns_ttl_to_wire(towire, 3600));
         TOWIRE_CHECK("rdlength_begin ", towire, dns_rdlength_begin(towire));
-        if (my_name != NULL) {
+        if (0) {
+#if STUB_ROUTER
+        } else if (srp_servers->stub_router_enabled && my_name != NULL) {
             TOWIRE_CHECK(my_name, towire, dns_full_name_to_wire(NULL, towire, my_name));
+#endif
+        } else if (uuid_name[0] != 0) {
+            TOWIRE_CHECK("uuid_name", towire, dns_name_to_wire(NULL, towire, uuid_name));
+            TOWIRE_CHECK("&query->enclosing_domain_pointer 2", towire,
+                         dns_pointer_to_wire(NULL, towire, &query->enclosing_domain_pointer));
         } else {
             TOWIRE_CHECK("\"ns\"", towire, dns_name_to_wire(NULL, towire, "ns"));
             TOWIRE_CHECK("&query->enclosing_domain_pointer 2", towire,
@@ -2265,19 +2453,24 @@
         }
 
         // Response is authoritative and not recursive.
+    authoritative:
         mask = ~dns_flags_ra;
         bitfield = bitfield | dns_flags_aa | tc;
         bitfield = bitfield & mask;
     } else {
         // Response is recursive and not authoritative.
-        mask = ~dns_flags_aa;
-        bitfield = bitfield | dns_flags_ra | tc;
-        bitfield = bitfield & mask;
+        if (question->type != dns_rrtype_soa) {
+            mask = ~dns_flags_aa;
+            bitfield = bitfield | dns_flags_ra | tc;
+            bitfield = bitfield & mask;
+        } else {
+            goto authoritative;
+        }
     }
 
-    INFO("[QID %x] query %p ->p %p ->lim %p len %zd rcode %d " PUB_S_SRP, ntohs(query->message->wire.id), query, query->towire.p,
-         &query->towire.message->data[0], query->towire.p - &query->towire.message->data[0],
-         dns_rcode_get(query->response), context_description);
+    INFO("[Q%d][QU%d][QID %x] ->p %p ->lim %p len %zd rcode %d " PUB_S_SRP, SERIAL(query), SERIAL(question),
+         ntohs(query->message->wire.id), query->towire.p, &query->towire.message->data[0],
+         query->towire.p - &query->towire.message->data[0], dns_rcode_get(query->response), context_description);
 
     // In the case that we get an error looking something up, we return that error immediately on the query that failed,
     // rather than trying to assemble a complete answer. In returning the error, we cancel any outstanding queries.
@@ -2293,7 +2486,7 @@
         if (send_query == NULL) {
 #ifdef DNSSD_PROXY_DUMP_TRACKER_QUERIES
             if (query->tracker == NULL) {
-                ERROR("query->tracker is NULL");
+                ERROR("[Q%d] query->tracker is NULL", SERIAL(query));
             } else {
                 char logbuf[200];
                 char *lbp = logbuf;
@@ -2301,10 +2494,10 @@
                 char *lbrestart;
                 bool print_last = true;
                 if (query->tracker->connection != NULL && query->tracker->connection->tcp_stream) {
-                    int len = snprintf(logbuf, sizeof(logbuf), "TCP %p %d: ", query, query->num_questions);
+                    int len = snprintf(logbuf, sizeof(logbuf), "[Q%d] TCP %d: ", SERIAL(query), query->num_questions);
                     lbrestart = logbuf + len;
                 } else {
-                    int len = snprintf(logbuf, sizeof(logbuf), "UDP %p %d: ", query, query->num_questions);
+                    int len = snprintf(logbuf, sizeof(logbuf), "[Q%d] UDP %p %d: ", SERIAL(query), query->num_questions);
                     lbrestart = logbuf + len;
                 }
                 lbp = logbuf + strlen(logbuf);
@@ -2320,14 +2513,14 @@
                         print_last = true;
                     } else {
                         *lbp = 0;
-                        INFO(PUB_S_SRP, logbuf);
+                        INFO("[Q%d] " PUB_S_SRP, SERIAL(query), logbuf);
                         lbp = lbrestart;
                         *lbp = 0;
                         print_last = false;
                     }
                 }
                 if (print_last) {
-                    INFO(PUB_S_SRP, logbuf);
+                    INFO("[Q%d] " PUB_S_SRP, SERIAL(query), logbuf);
                 }
             }
 #endif // DNSSD_PROXY_DUMP_TRACKER_QUERIES
@@ -2383,7 +2576,7 @@
         }
 
         if (towire->error) {
-            ERROR("failed on %s", failnote);
+            ERROR("[Q%d][QU%d][QID%x] failed on %s", SERIAL(query), SERIAL(question), ntohs(send_query->message->wire.id), failnote);
             if (tc == dns_flags_tc) {
                 dns_rcode_set(send_query->response, dns_rcode_noerror);
             } else {
@@ -2399,7 +2592,7 @@
 
     iov.iov_len = (send_query->towire.p - (uint8_t *)send_query->response);
     iov.iov_base = send_query->response;
-    INFO("[QID %x] (len %zd)", ntohs(send_query->message->wire.id), iov.iov_len);
+    INFO("[Q%d][QU%d] (len %zd)", SERIAL(query), SERIAL(question), iov.iov_len);
 
     ioloop_send_message(send_query->tracker->connection, send_query->message, &iov, 1);
 
@@ -2435,7 +2628,7 @@
         TOWIRE_CHECK("kDSOType_DNSPushUpdate", &query->towire,
                      dns_u16_to_wire(&query->towire, kDSOType_DNSPushUpdate));
         if (query->towire.p + 2 > query->towire.lim) {
-            ERROR("No room for dso length in DNS Push notification message.");
+            ERROR("[Q%d] No room for dso length in DNS Push notification message.", SERIAL(query));
             dp_query_towire_reset(query);
             return;
         }
@@ -2443,14 +2636,23 @@
         query->towire.p += 2;
     }
     if (failnote != NULL) {
-        ERROR("couldn't start update: %s", failnote);
+        ERROR("[Q%d] couldn't start update: %s", SERIAL(query), failnote);
     }
 }
 
 static void
-dp_push_response(dnssd_query_t *query)
+dp_push_response(dnssd_query_t *query, dns_rr_t *original_question)
 {
     struct iovec iov;
+    question_t *question = query->question;
+    char nbuf[DNS_MAX_NAME_SIZE_ESCAPED + 1];
+    char *name = "<null question name>";
+    if (question != NULL) {
+        name = question->name;
+    } else if (original_question != NULL) {
+        dns_name_print(original_question->name, nbuf, sizeof(nbuf));
+        name = nbuf;
+    }
 
     VALIDATE_TRACKER_CONNECTION_NON_NULL();
 
@@ -2458,7 +2660,7 @@
         int16_t dso_length = query->towire.p - query->p_dso_length - 2;
         iov.iov_len = (query->towire.p - (uint8_t *)query->response);
         iov.iov_base = query->response;
-        INFO("" PRI_S_SRP " (len %zd)", query->question->name, iov.iov_len);
+        INFO("[Q%d][QU%d] " PRI_S_SRP " (len %zd)", SERIAL(query), SERIAL(question), name, iov.iov_len);
 
         query->towire.p = query->p_dso_length;
         dns_u16_to_wire(&query->towire, dso_length);
@@ -2471,36 +2673,123 @@
 dnssd_hardwired_response(dnssd_query_t *query, DNSServiceQueryRecordReply UNUSED callback)
 {
     hardwired_t *hp;
-    bool got_response = false;
+    question_t *question = query->question;
+    const char *response_type = NULL;
+    uint8_t v4mapped[12] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff };
 
-    for (hp = query->question->served_domain->hardwired_responses; hp; hp = hp->next) {
-        if ((query->question->type == hp->type || query->question->type == dns_rrtype_any) &&
-            query->question->qclass == dns_qclass_in && !strcasecmp(hp->name, query->question->name)) {
-            if (query->dso != NULL) {
-                dns_push_start(query);
-                // Since hardwired response is set by the dnssd-proxy itself, do not do ".local" translation.
-                dp_query_add_data_to_response(query, hp->fullname, hp->type, dns_qclass_in, hp->rdlen, hp->rdata, 3600, true);
+    // If the question is for our uuid name in a domain we're authoritative for, respond with the IP address that
+    // the question was received on.
+    if (!strcasecmp(question->name, uuid_name) && (question->type == dns_rrtype_a || question->type == dns_rrtype_aaaa)) {
+        addr_t *local = NULL;
+        if (query->message != NULL) {
+            local = &query->message->local;
+        } else {
+            local = &query->dso->transport->local;
+        }
+
+        // If it's an IPv4 address we can respond with an A record.
+        if (question->type == dns_rrtype_a && local->sa.sa_family == AF_INET) {
+            dp_query_add_data_to_response(query, question->name, question->type, dns_qclass_in, 4,
+                                          &local->sin.sin_addr, 300, true, true, &query->response->ancount);
+            response_type = "local host IPv4 address";
+        }
+        // If it's an IPv4-mapped IPv6 address, we can respond with an A record
+        else if (local->sa.sa_family == AF_INET6 && question->type == dns_rrtype_a &&
+                 !memcmp(&local->sin6.sin6_addr, v4mapped, sizeof(v4mapped)))
+        {
+            dp_query_add_data_to_response(query, question->name, question->type, dns_qclass_in, 4,
+                                          ((uint8_t *)&local->sin6.sin6_addr) + 12, 3600, true, true,
+                                          &query->response->ancount);
+            response_type = "local host v4-mapped address";
+        }
+        // If it's an IPv6 address and NOT a v4-mapped address, we can respond with an AAAA record.
+        else if (local->sa.sa_family == AF_INET6 && question->type == dns_rrtype_aaaa &&
+                 memcmp(&local->sin6.sin6_addr, v4mapped, sizeof(v4mapped)))
+        {
+            struct in6_addr response = local->sin6.sin6_addr;
+            bool address_is_usable = false;
+            // If it's not a synthesized anycast or rloc address, we can just use it.
+            if (!is_thread_mesh_synthetic_address(&response)) {
+                address_is_usable = true;
+                response_type = "local host IPv6 address";
             } else {
-                // Store the response
-                if (!query->towire.truncated) {
-                    // Since hardwired response is set by the dnssd-proxy itself, do not do ".local" translation.
-                    bool record_added = dp_query_add_data_to_response(query, hp->fullname, hp->type, dns_qclass_in,
-                        hp->rdlen, hp->rdata, 3600, true);
-                    if (!query->towire.truncated) {
-                        query->response->ancount = htons(ntohs(query->response->ancount) + (record_added ? 1 : 0));
+                // Otherwise, we need to find the mesh-local address and respond with that.
+                srp_server_t *server_state = srp_servers;
+#if SRP_TEST_SERVER
+                for (; !address_is_usable && server_state != NULL; server_state = server_state->next)
+#endif
+                {
+                    if (0) {
+#if STUB_ROUTER
+                    } else if (server_state->stub_router_enabled) {
+                        route_state_t *route_state = server_state->route_state;
+                        if (route_state->thread_interface_name != NULL) {
+                            for (interface_address_state_t *address = route_state->interface_addresses;
+                                 !address_is_usable && address != NULL; address = address->next)
+                            {
+                                // Wrong interface or wrong type of address
+                                if (strcmp(address->name, route_state->thread_interface_name) ||
+                                    address->addr.sa.sa_family != AF_INET6)
+                                {
+                                    continue;
+                                }
+                                if (!is_thread_mesh_synthetic_or_link_local(&address->addr.sin6.sin6_addr))
+                                {
+                                    memcpy(&response, &address->addr.sin6.sin6_addr, sizeof(response));
+                                    response_type = "thread interface address";
+                                    address_is_usable = true;
+                                }
+                            }
+                            if (address_is_usable == false) {
+                                response_type = "no usable address on thread interface";
+                            }
+                        } else {
+                            address_is_usable = false;
+                            response_type = "thread interface name unknown";
+                        }
+#endif
+                    } else {
+                        // For thread device, the only thing that can work is the ML-EID.
+                        if (service_publisher_get_ml_eid(server_state->service_publisher, &response)) {
+                            address_is_usable = true;
+                            response_type = "thread device ML-EID";
+                        } else {
+                            response_type = "thread ML-EID not known";
+                        }
                     }
                 }
             }
-            got_response = true;
+            if (address_is_usable) {
+                SEGMENTED_IPv6_ADDR_GEN_SRP(&response, response_buf);
+                INFO(PUB_S_SRP " IN AAAA " PRI_SEGMENTED_IPv6_ADDR_SRP " " PUB_S_SRP, question->name,
+                     SEGMENTED_IPv6_ADDR_PARAM_SRP(&response, response_buf), response_type);
+                dp_query_add_data_to_response(query, question->name, question->type, dns_qclass_in, 16,
+                                              &response, 300, true, true, &query->response->ancount);
+            }
+        }
+    } else {
+        for (hp = query->question->served_domain->hardwired_responses; hp; hp = hp->next) {
+            if ((query->question->type == hp->type || query->question->type == dns_rrtype_any) &&
+                query->question->qclass == dns_qclass_in && !strcasecmp(hp->name, query->question->name))
+            {
+                if (query->dso != NULL) {
+                    // Since hardwired response is set by the dnssd-proxy itself, do not do ".local" translation.
+                    dp_query_add_data_to_response(query, hp->fullname, hp->type, dns_qclass_in, hp->rdlen, hp->rdata,
+                                                  3600, true, false, NULL);
+                } else {
+                    // Store the response
+                    if (!query->towire.truncated) {
+                        // Since hardwired response is set by the dnssd-proxy itself, do not do ".local" translation.
+                        dp_query_add_data_to_response(query, hp->fullname, hp->type, dns_qclass_in,
+                                                      hp->rdlen, hp->rdata, 3600, true, false,
+                                                      &query->response->ancount);
+                    }
+                }
+                response_type = "hardwired";
+            }
         }
     }
-    if (got_response) {
-        if (query->dso != NULL) {
-            dp_push_response(query);
-        } else {
-            // Send the answer(s).
-            dp_query_send_dns_response(query, "hardwired");
-        }
+    if (response_type != NULL) {
         return true;
     }
     return false;
@@ -2524,9 +2813,12 @@
     memcpy(rdata, prefix->s6_addr, sizeof(rdata));
     for (size_t i = 0; i < countof(ipv4_addrs);) {
         memcpy(&rdata[12], ipv4_addrs[i], 4);
-        const bool added = dp_query_add_data_to_response(query, "ipv4only.arpa.", dns_rrtype_aaaa, query->question->qclass,
-                                                         (uint16_t)sizeof(rdata), rdata, RFC8766_TTL_CLAMP, true);
+        uint8_t *revert = query->towire.p;
+        dp_query_add_data_to_response(query, "ipv4only.arpa.", dns_rrtype_aaaa, query->question->qclass,
+                                      (uint16_t)sizeof(rdata), rdata, RFC8766_TTL_CLAMP, true, false,
+                                      &query->response->arcount);
         if (query->towire.truncated) {
+            query->towire.p = revert;
             if (query->tracker->connection->tcp_stream) {
                 if (embiggen(query)) {
                     query->towire.truncated = false;
@@ -2538,9 +2830,6 @@
             }
             return;
         }
-        if (added) {
-            query->response->arcount = htons(ntohs(query->response->arcount) + 1);
-        }
         i++;
     }
 }
@@ -2549,56 +2838,55 @@
 static void
 dns_query_answer_process(DNSServiceFlags flags, DNSServiceErrorType errorCode,
                          const char *fullname, uint16_t rrtype, uint16_t rrclass,
-                         uint16_t rdlen, const void *rdata, uint32_t ttl, dnssd_query_t *query)
+                         uint16_t rdlen, const void *rdata, uint32_t ttl, dnssd_query_t *query, bool send)
 {
-    bool record_added;
+    question_t *question = query->question;
 
-    INFO(PRI_S_SRP PUB_S_SRP " %d %d %x %d %p", fullname, (flags & kDNSServiceFlagsMoreComing) ? " m" : "",
-         rrtype, rrclass, rdlen, errorCode, query);
+    INFO("[Q%d][QU%d] " PRI_S_SRP PUB_S_SRP PUB_S_SRP " %d %x %d %p", SERIAL(query), SERIAL(question),
+         fullname, (flags & kDNSServiceFlagsMoreComing) ? " m " : " ", dns_rrtype_to_string(rrtype), rrclass, rdlen,
+         errorCode, query);
 
     VALIDATE_TRACKER_CONNECTION_NON_NULL();
 
     if (errorCode == kDNSServiceErr_NoError) {
 #if SRP_FEATURE_NAT64
-        const bool aaaa_query_got_a_record = (query->question->type == dns_rrtype_aaaa) && (rrtype == dns_rrtype_a);
+        const bool aaaa_query_got_a_record = (question->type == dns_rrtype_aaaa) && (rrtype == dns_rrtype_a);
         if (srp_servers->srp_nat64_enabled && (ntohs(query->response->arcount) != 0) && !aaaa_query_got_a_record) {
             return;
         }
 #endif
     re_add:
-        record_added = dp_query_add_data_to_response(query, fullname, rrtype, rrclass, rdlen, rdata,
-            ttl > RFC8766_TTL_CLAMP ? RFC8766_TTL_CLAMP : ttl, false);
-        if (query->towire.truncated) {
-            if (query->tracker->connection->tcp_stream) {
-                if (embiggen(query)) {
-                    query->towire.truncated = false;
-                    query->towire.error = false;
-                    goto re_add;
-                } else {
-                    dns_rcode_set(query->response, dns_rcode_servfail);
-                    dp_query_send_dns_response(query, "failed embiggen");
-                    return;
-                }
-            }
-        } else {
+        if (send) {
+            uint16_t *counter = &query->response->ancount;
 #if SRP_FEATURE_NAT64
-            if (record_added) {
-                if (srp_servers->srp_nat64_enabled && aaaa_query_got_a_record) {
-                    query->response->arcount = htons(ntohs(query->response->arcount) + 1);
-                } else {
-                    query->response->ancount = htons(ntohs(query->response->ancount) + 1);
+            if (srp_servers->srp_nat64_enabled && aaaa_query_got_a_record) {
+                counter = &query->response->arcount;
+            }
+#endif
+            uint8_t *revert = query->towire.p;
+            dp_query_add_data_to_response(query, fullname, rrtype, rrclass, rdlen, rdata,
+                                          ttl > RFC8766_TTL_CLAMP ? RFC8766_TTL_CLAMP : ttl, false, false, counter);
+            if (query->towire.truncated) {
+                query->towire.p = revert;
+                if (query->tracker->connection->tcp_stream) {
+                    if (embiggen(query)) {
+                        query->towire.truncated = false;
+                        query->towire.error = false;
+                        goto re_add;
+                    } else {
+                        dns_rcode_set(query->response, dns_rcode_servfail);
+                        dp_query_send_dns_response(query, "failed embiggen");
+                        return;
+                    }
                 }
             }
-#else
-            query->response->ancount = htons(ntohs(query->response->ancount) + (record_added ? 1 : 0));
-#endif
         }
         // If there isn't more coming, send the response now
         if (!(flags & kDNSServiceFlagsMoreComing) || query->towire.truncated) {
             // When we get a CNAME response, we may not get the record it points to with the MoreComing
             // flag set, so don't respond yet.
-            if (query->question->type != dns_rrtype_cname && rrtype == dns_rrtype_cname) {
-                INFO("not responding yet because CNAME.");
+            if (question->type != dns_rrtype_cname && rrtype == dns_rrtype_cname) {
+                INFO("[Q%d][QU%d] not responding yet because CNAME.", SERIAL(query), SERIAL(question));
             } else {
 #if SRP_FEATURE_NAT64
                 if (srp_servers->srp_nat64_enabled && (ntohs(query->response->arcount) != 0)) {
@@ -2642,56 +2930,69 @@
 static void
 dns_push_query_answer_process(DNSServiceFlags flags, DNSServiceErrorType errorCode,
                               const char *fullname, uint16_t rrtype, uint16_t rrclass,
-                              uint16_t rdlen, const void *rdata, uint32_t ttl, dnssd_query_t *query);
+                              uint16_t rdlen, const void *rdata, uint32_t ttl, dnssd_query_t *query, bool send);
 
 // This is the callback for both dns query and dns push query results.
 static void
-dns_question_callback(DNSServiceRef UNUSED sdRef, DNSServiceFlags flags, uint32_t UNUSED interfaceIndex,
+dns_question_callback(DNSServiceRef UNUSED sdRef, DNSServiceFlags flags, uint32_t interfaceIndex,
                       DNSServiceErrorType errorCode, const char *fullname, uint16_t rrtype, uint16_t rrclass,
                       uint16_t rdlen, const void *rdata, uint32_t ttl, void *context)
 {
     question_t *question = context;
     dnssd_query_t *query, *next;
+    bool send = true;
 
     // For dns push query,  insert or remove answer from the question cache depending on the flags
     // For dns query (dso==NULL), add answer when receiving callback to the question
     if (errorCode == kDNSServiceErr_NoError) {
         if (flags & kDNSServiceFlagsAdd) {
-            // Add
-            // the extra space rdlen stores rdata at the end
-            answer_t *answer = calloc(1, sizeof(*answer) + rdlen);
-            if (answer == NULL) {
-                ERROR("unable to allocate memory for answer - "
-                      "name: " PRI_S_SRP ", rrtype: " PUB_S_SRP ", rrclass: " PUB_S_SRP ", rdlen: %u.",
-                      fullname, dns_rrtype_to_string(rrtype), dns_qclass_to_string(rrclass), rdlen);
-                return;
+            // Eliminate duplicates (appears on more than one interface)
+            for (answer_t *answer = question->answers; answer != NULL; answer = answer->next) {
+                if (answer_match(answer, rdlen, fullname, rrtype, rrclass, rdata)) {
+                    INFO("[QU%d] duplicate answer in cache - name: " PRI_S_SRP ", rrtype: " PUB_S_SRP
+                         ", rrclass: " PUB_S_SRP ", rdlen: %u." PUB_S_SRP, SERIAL(question),
+                         fullname, dns_rrtype_to_string(rrtype), dns_qclass_to_string(rrclass), rdlen,
+                         (flags & kDNSServiceFlagsMoreComing) ? " more coming" : " done");
+                    send = false;
+                    break;
+                }
             }
-            answer->fullname = strdup(fullname);
-            if (answer->fullname == NULL) {
-                ERROR("strdup failed to copy the answer name: " PRI_S_SRP, fullname);
-                free(answer);
-                return;
-            }
-            answer->interface_index = interfaceIndex;
-            answer->ttl = ttl;
-            answer->rrtype = rrtype;
-            answer->rrclass = rrclass;
-            answer->rdlen = rdlen;
-            answer->rdata = (uint8_t *)(answer + 1);
-            memcpy(answer->rdata, rdata, rdlen);
-            answer->next = NULL;
-            // Insert answer at the tail
-            answer_t **tail = &(question->answers);
-            while (*tail != NULL) {
-                tail = &((*tail)->next);
-            }
-            *tail = answer;
-            // Received data; reset no_data flag.
-            question->no_data = false;
-            INFO("add answer to cache - "
-                 "name: " PRI_S_SRP ", rrtype: " PUB_S_SRP ", rrclass: " PUB_S_SRP ", rdlen: %u." PUB_S_SRP,
-                 fullname, dns_rrtype_to_string(rrtype), dns_qclass_to_string(rrclass), rdlen,
+            if (send) {
+                // Add the extra space rdlen stores rdata at the end
+                answer_t *answer = calloc(1, sizeof(*answer) + rdlen);
+                if (answer == NULL) {
+                    ERROR("[QU%d] unable to allocate memory for answer - name: " PRI_S_SRP ", rrtype: " PUB_S_SRP
+                          ", rrclass: " PUB_S_SRP ", rdlen: %u.", SERIAL(question),
+                          fullname, dns_rrtype_to_string(rrtype), dns_qclass_to_string(rrclass), rdlen);
+                    return;
+                }
+                answer->fullname = strdup(fullname);
+                if (answer->fullname == NULL) {
+                    ERROR("[QU%d] strdup failed to copy the answer name: " PRI_S_SRP, SERIAL(question), fullname);
+                    free(answer);
+                    return;
+                }
+                answer->interface_index = interfaceIndex;
+                answer->ttl = ttl;
+                answer->rrtype = rrtype;
+                answer->rrclass = rrclass;
+                answer->rdlen = rdlen;
+                answer->rdata = (uint8_t *)(answer + 1);
+                memcpy(answer->rdata, rdata, rdlen);
+                answer->next = NULL;
+                // Insert answer at the tail
+                answer_t **tail = &(question->answers);
+                while (*tail != NULL) {
+                    tail = &((*tail)->next);
+                }
+                *tail = answer;
+                // Received data; reset no_data flag.
+                question->no_data = false;
+                INFO("[QU%d] add answer to cache - name: " PRI_S_SRP ", rrtype: " PUB_S_SRP
+                     ", rrclass: " PUB_S_SRP ", rdlen: %u." PUB_S_SRP, SERIAL(question),
+                     fullname, dns_rrtype_to_string(rrtype), dns_qclass_to_string(rrclass), rdlen,
                  (flags & kDNSServiceFlagsMoreComing) ? " more coming" : " done");
+            }
         } else {
             // Remove
             answer_t **answer = &(question->answers);
@@ -2700,12 +3001,12 @@
             while (*answer != NULL) {
                 cur = *answer;
                 if (answer_match(cur, rdlen, fullname, rrtype, rrclass, rdata)) {
-                    INFO("remove answer from cache - "
+                    INFO("[QU%d] remove answer from cache - "
                          "name: " PRI_S_SRP ", rrtype: " PUB_S_SRP ", rrclass: " PUB_S_SRP ", rdlen: %u." PUB_S_SRP,
-                         fullname, dns_rrtype_to_string(rrtype), dns_qclass_to_string(rrclass), rdlen,
+                         SERIAL(question), fullname, dns_rrtype_to_string(rrtype), dns_qclass_to_string(rrclass), rdlen,
                          (flags & kDNSServiceFlagsMoreComing) ? " more coming" : " done");
                     *answer = cur->next;
-                    answer_free(cur);
+                    dp_answer_free(cur);
                     matched = true;
                     // If individual RR to be removed, get out of the loop once the RR has been removed
                     if (rdlen != 0) {
@@ -2716,8 +3017,8 @@
                 }
             }
             if (!matched) {
-                INFO("remove not found in cache - "
-                     "name: " PRI_S_SRP ", rrtype: " PUB_S_SRP ", rrclass: " PUB_S_SRP ", rdlen: %u." PUB_S_SRP,
+                INFO("[Q%d] remove not found in cache - name: " PRI_S_SRP
+                     ", rrtype: " PUB_S_SRP ", rrclass: " PUB_S_SRP ", rdlen: %u." PUB_S_SRP, SERIAL(question),
                      fullname, dns_rrtype_to_string(rrtype), dns_qclass_to_string(rrclass), rdlen,
                      (flags & kDNSServiceFlagsMoreComing) ? " more coming" : " done");
             }
@@ -2727,30 +3028,35 @@
             }
         }
     } else if (errorCode == kDNSServiceErr_NoSuchRecord) {
-        INFO("no data - name: " PRI_S_SRP ", rrtype: " PUB_S_SRP ", rrclass: " PUB_S_SRP ", rdlen: %u." PUB_S_SRP,
-             fullname, dns_rrtype_to_string(rrtype), dns_qclass_to_string(rrclass), rdlen,
-             (flags & kDNSServiceFlagsMoreComing) ? " more coming" : " done");
+        INFO("[QU%d] no data - name: " PRI_S_SRP ", rrtype: " PUB_S_SRP
+             ", rrclass: " PUB_S_SRP ", rdlen: %u." PUB_S_SRP, SERIAL(question), fullname, dns_rrtype_to_string(rrtype),
+             dns_qclass_to_string(rrclass), rdlen, (flags & kDNSServiceFlagsMoreComing) ? " more coming" : " done");
         question->no_data = true;
-#if SRP_FEATURE_DNSSD_PROXY_SHARED_CONNECTIONS
     } else if (errorCode == kDNSServiceErr_ServiceNotRunning || errorCode == kDNSServiceErr_DefunctConnection) {
+#if SRP_FEATURE_DNSSD_PROXY_SHARED_CONNECTIONS
         if (shared_discovery_txn != NULL) {
             ioloop_dnssd_txn_cancel(shared_discovery_txn);
             ioloop_dnssd_txn_release(shared_discovery_txn);
             shared_discovery_txn = NULL;
-            dp_handle_server_disconnect();
+            dp_handle_server_disconnect(NULL, errorCode);
         }
+#else
+        ioloop_dnssd_txn_cancel(question->txn);
+        ioloop_dnssd_txn_release(question->txn);
+        question->txn = NULL;
+        dp_handle_server_disconnect(NULL, errorCode);
+#endif
         return; // This doesn't count as a result.
-#endif // SRP_FEATURE_DNSSD_PROXY_SHARED_CONNECTIONS
     }
     query = question->queries;
     while(query != NULL) {
         next = query->question_next;
         if (query->dso != NULL) {
             dns_push_query_answer_process(flags, errorCode, fullname, rrtype, rrclass,
-                                          rdlen, rdata, ttl, query);
+                                          rdlen, rdata, ttl, query, send);
         } else {
             dns_query_answer_process(flags, errorCode, fullname, rrtype, rrclass,
-                                     rdlen, rdata, ttl, query);
+                                     rdlen, rdata, ttl, query, send);
         }
         query = next;
     }
@@ -2763,33 +3069,34 @@
     dnssd_query_t *query = context;
     char name[DNS_MAX_NAME_SIZE + 1];
     size_t namelen = strlen(query->question->name);
+    question_t *question = query->question;
 
-    if (query->question->answers != NULL) {
-        FAULT("answers present, but dp_query_wakeup reached for query %p question %p name " PRI_S_SRP,
-              query, query->question, query->question->name);
+    if (question->answers != NULL) {
+        FAULT("[Q%d][QU%d] answers present, but dp_query_wakeup reached for name " PRI_S_SRP,
+              SERIAL(query), SERIAL(question), question->name);
     } else {
-        query->question->no_data = true;
+        question->no_data = true;
     }
 
     // Should never happen.
-    if (namelen + (query->question->served_domain
-                   ? (query->question->served_domain->interface != NULL
+    if (namelen + (question->served_domain
+                   ? (question->served_domain->interface != NULL
                       ? sizeof local_suffix
                       // XXX why are we checking this but not copying in the served domain name below?
-                      : strlen(query->question->served_domain->domain_ld) + 1)
+                      : strlen(question->served_domain->domain_ld) + 1)
                    : 0) > sizeof name) {
-        ERROR("no space to construct name.");
+        ERROR("[Q%d][QU%d] no space to construct name.", SERIAL(query), SERIAL(question));
         dnssd_query_cancel(query);
         return;
     }
 
-    memcpy(name, query->question->name, namelen + 1);
-    if (query->question->served_domain != NULL) {
+    memcpy(name, question->name, namelen + 1);
+    if (question->served_domain != NULL) {
         memcpy(name + namelen, local_suffix, sizeof(local_suffix));
     }
     RETAIN_HERE(query, dnssd_query);
     dp_query_send_dns_response(query, "query wakeup");
-    dp_question_cache_remove_queries(query->question);
+    dp_question_cache_remove_queries(question);
     RELEASE_HERE(query, dnssd_query);
 }
 
@@ -2835,11 +3142,12 @@
                              ERROR("unable to allocate memory for question name on " PRI_S_SRP, name));
         new_question->type = search_term->type;
         new_question->qclass = search_term->qclass;
-        new_question->start_time = (int64_t)time(NULL);
+        new_question->start_time = srp_utime();
         new_question->answers = NULL;
         new_question->served_domain = sdt;
         new_question->queries = NULL;
         new_question->no_data = false;
+        new_question->serial = ++cur_question_serial;
 
         if (sdt != NULL && sdt->interface != NULL) {
             new_question->interface_index = sdt->interface->ifindex;
@@ -2869,46 +3177,55 @@
     return ret;
 }
 
-// Look for answers in the cache for the current query
+// Look for answers in the cache for the current query. Remove flag is used when mDNSResponder connection is broken, to signal to
+// DNS Push clients only that the records previously sent should be discarded.
 static void
-dp_query_reply_from_cache(question_t *question, dnssd_query_t *query)
+dp_query_reply_from_cache(question_t *question, dnssd_query_t *query, bool remove)
 {
     // For dns query, if no_data is flagged or it's been six seconds since the question
     // was started and there is still no answer yet, we should also respond immediately.
     // [DNS Discovery Proxy RFC, RFC 8766, Section 5.6]
+    // Note that six seconds as stated in RFC8766 is probably too long, currently we're using 800ms.
     if (query->dso == NULL &&
         (question->no_data == true ||
          (question->answers == NULL &&
-          time(NULL) - question->start_time > RESPONSE_WINDOW)))
+          srp_utime() - question->start_time > RESPONSE_WINDOW_USECS)))
     {
-        INFO("no data for question - type %d class %d " PRI_S_SRP,
-             question->type, question->qclass, question->name);
+        INFO("[Q%d][QU%d] no data for question - type %d class %d " PRI_S_SRP,
+             SERIAL(query), SERIAL(question), question->type, question->qclass, question->name);
         dns_query_answer_process(0, kDNSServiceErr_NoSuchRecord, question->name,
                                  question->type, question->qclass, 0,
-                                 NULL, 0, query);
+                                 NULL, 0, query, true);
         dp_question_cache_remove_queries(question);
         return;
     }
     // answers are available for the question being asked
     if (question->answers != NULL) {
-        INFO("reply from cache for question - type %d class %d " PRI_S_SRP,
-             question->type, question->qclass, question->name);
+        INFO("[Q%d][QU%d] reply from cache for question - type %d class %d " PRI_S_SRP,
+             SERIAL(query), SERIAL(question), question->type, question->qclass, question->name);
         DNSServiceFlags flags;
         answer_t *answer = question->answers;
         while (answer != NULL) {
-            flags = kDNSServiceFlagsAdd;
+            if (remove) {
+                flags = 0;
+            } else {
+                flags = kDNSServiceFlagsAdd;
+            }
+
             answer_t *next = answer->next;
             if (next != NULL) {
                 flags |= kDNSServiceFlagsMoreComing;
             }
             if (query->dso == NULL) {
-                dns_query_answer_process(flags, kDNSServiceErr_NoError, answer->fullname,
-                                         answer->rrtype, answer->rrclass, answer->rdlen,
-                                         answer->rdata, answer->ttl, query);
+                if (!remove) {
+                    dns_query_answer_process(flags, kDNSServiceErr_NoError, answer->fullname,
+                                             answer->rrtype, answer->rrclass, answer->rdlen,
+                                             answer->rdata, answer->ttl, query, true);
+                }
             } else {
                 dns_push_query_answer_process(flags, kDNSServiceErr_NoError, answer->fullname,
                                               answer->rrtype, answer->rrclass, answer->rdlen,
-                                              answer->rdata, answer->ttl, query);
+                                              answer->rdata, answer->ttl, query, true);
             }
             answer = next;
         }
@@ -2916,19 +3233,24 @@
     }
 }
 
+static void
+dp_query_context_release(void *context)
+{
+    dnssd_query_t *query = context;
+    RELEASE_HERE(query, dnssd_query);
+}
+
 static bool
-dp_query_start(dnssd_query_t *query, int *rcode, bool dns64)
+dp_query_start(dnssd_query_t *query, int *rcode, bool *hardwired, bool dns64)
 {
     bool local = false;
     question_t *question = query->question;
 
     if (question->served_domain != NULL) {
         if (dnssd_hardwired_response(query, dns_question_callback)) {
-            *rcode = dns_rcode_noerror;
-            INFO("hardwired response");
-            dp_question_cache_remove_queries(query->question);
-            RELEASE_HERE(query->question, question);
-            query->question = NULL;
+            *rcode = dns_rcode_noerror; // indicate that we already sent the response
+            *hardwired = true;
+            INFO("[Q%d] hardwired response", SERIAL(query));
             return true;
         }
         local = true;
@@ -2937,28 +3259,29 @@
     // If we get an SOA query for record that's under a zone cut we're authoritative for, which
     // is the case of query->served_domain->interface != NULL, then answer with a negative response that includes
     // our authority records, rather than waiting for the query to time out.
-    if (question->served_domain != NULL && question->served_domain->interface != NULL &&
-        (question->type == dns_rrtype_soa ||
-         question->type == dns_rrtype_ns ||
-         question->type == dns_rrtype_ds) && question->qclass == dns_qclass_in && query->dso == NULL)
-    {
-        dp_query_send_dns_response(query, "query start");
-        dp_question_cache_remove_queries(query->question);
-        RELEASE_HERE(query->question, question);
-        query->question = NULL;
+    if (question->served_domain != NULL && question->served_domain->interface != NULL && !question->name[0]) {
+        // If this isn't a DNS Push query, we can signal that there is no data. Otherwise we just never send an answer since
+        // we will never have one.
+        if (query->dso == NULL) {
+            *hardwired = true;
+        }
         return true;
     }
 
     // Check if DNSServiceQueryRecord call needs to be made
     if (question->txn == NULL) {
-        if (dp_start_question(question, dns64) != kDNSServiceErr_NoError) {
+        int ret = dp_start_question(question, dns64);
+        if (ret == kDNSServiceErr_Refused) {
             *rcode = dns_rcode_servfail;
-            INFO("couldn't start question");
+            INFO("question was refused");
+        } else if (ret != kDNSServiceErr_NoError) {
+            *rcode = dns_rcode_servfail;
+            INFO("[Q%d] couldn't start question", SERIAL(query));
             return false;
         }
     } else {
         if (question->answers != NULL || question->no_data) {
-            INFO("answering immediately from cache");
+            INFO("[Q%d] answering immediately from cache", SERIAL(query));
             *rcode = dns_rcode_noerror;
             return true;
         }
@@ -2969,6 +3292,11 @@
     // millisecond, so we'll wait 100ms.
     if (query->dso == NULL && local) {
         // [DNS Discovery Proxy RFC, RFC 8766, Section 5.6, Answer Aggregation]
+
+        // RFC8766 asks us to wait six seconds, but this is probably too long. Most likely we will have all
+        // our answers much sooner than that, and waiting this long means that we have to keep state for
+        // this long; when there are a lot of queries coming in, that can amount to too much state, causing
+        // us to drop requests we could easily have answered.
         if (query->wakeup == NULL) {
             query->wakeup = ioloop_wakeup_create();
             if (query->wakeup == NULL) {
@@ -2976,10 +3304,11 @@
                 return false;
             }
         }
-        ioloop_add_wake_event(query->wakeup, query, dp_query_wakeup, NULL, 6 * IOLOOP_SECOND);
+        ioloop_add_wake_event(query->wakeup, query, dp_query_wakeup, dp_query_context_release, RESPONSE_WINDOW_MSECS /* ms */);
+        RETAIN_HERE(query, dnssd_query);
     }
 
-    INFO("waiting for wakeup or response");
+    INFO("[Q%d] waiting for wakeup or response", SERIAL(query));
     return true;
 }
 
@@ -2992,23 +3321,25 @@
 
     dnssd_query_t *query = calloc(1,sizeof *query);
     require_action_quiet(query != NULL, exit, *rcode = dns_rcode_servfail;
-        ERROR("[QID %x] Unable to allocate memory for query on " PRI_S_SRP, xid, name));
+                         ERROR("Unable to allocate memory for query on " PRI_S_SRP, name));
     RETAIN_HERE(query, dnssd_query); // for the caller
+    query->serial = ++cur_query_serial;
 
     // If it's a query for a name served by the local discovery proxy, do an mDNS lookup.
-    if (sdt) {
-        INFO("[QID %x] msg %p " PUB_S_SRP " question: type %d class %d " PRI_S_SRP "." PRI_S_SRP " -> " PRI_S_SRP DOT_LOCAL,
-             xid, message, dso != NULL ? "push" : " dns", question->type, question->qclass, name, sdt->domain, name);
+    if (sdt != NULL) {
+        INFO("[Q%d][QID%x] msg %p " PUB_S_SRP " question: type %d class %d " PRI_S_SRP "." PRI_S_SRP " -> "
+             PRI_S_SRP DOT_LOCAL, SERIAL(query), xid, message, dso != NULL ? "push" : " dns",
+             question->type, question->qclass, name, sdt->domain, name);
     } else {
         dns_name_print(question->name, name, sizeof name);
-        INFO("[QID %x] msg %p " PUB_S_SRP " question: type %d class %d " PRI_S_SRP, xid, message,
-             dso != NULL ? "push" : " dns", question->type, question->qclass, name);
+        INFO("[Q%d][QID%x] msg %p " PUB_S_SRP " question: type %d class %d " PRI_S_SRP, SERIAL(query), xid,
+             message, dso != NULL ? "push" : " dns", question->type, question->qclass, name);
     }
 
     query->response = malloc(sizeof *query->response);
     require_action_quiet(query->response != NULL, exit, *rcode = dns_rcode_servfail;
-                         ERROR("[QID %x] Unable to allocate memory for query response on " PRI_S_SRP,
-                               xid, name));
+                         ERROR("[Q%d] Unable to allocate memory for query response on " PRI_S_SRP,
+                               SERIAL(query), name));
 
     query->data_size = DNS_DATA_SIZE;
 
@@ -3045,7 +3376,7 @@
     *qr = query;
     // Question query list holds a reference to the query.
     RETAIN_HERE(*qr, dnssd_query);
-    INFO("[QID %x] msg %p " PUB_S_SRP " cache entry for question: type %d class %d " PRI_S_SRP,
+    INFO("[Q%d][QID%x] msg %p " PUB_S_SRP " cache entry for question: type %d class %d " PRI_S_SRP, SERIAL(query),
          xid, query->message, new_entry ? "new" : " existing", question->type, question->qclass, name);
     *rcode = dns_rcode_noerror;
     dp_num_outstanding_queries++;
@@ -3062,7 +3393,7 @@
 static void
 dns_push_query_answer_process(DNSServiceFlags flags, DNSServiceErrorType errorCode,
                               const char *fullname, uint16_t rrtype, uint16_t rrclass,
-                              uint16_t rdlen, const void *rdata, uint32_t ttl, dnssd_query_t *query)
+                              uint16_t rdlen, const void *rdata, uint32_t ttl, dnssd_query_t *query, bool send)
 {
     uint8_t *revert = query->towire.p;
 
@@ -3078,63 +3409,63 @@
     // all outstanding queries that aren't waiting on a time trigger.   This is because more-coming isn't
     // query-specific
 
-    INFO("PUSH " PRI_S_SRP " %d %d %x %d %p", fullname, rrtype, rrclass, rdlen, errorCode, query);
+    INFO("[Q%d] PUSH " PRI_S_SRP " %d %d %x %d %p", SERIAL(query), fullname, rrtype, rrclass, rdlen, errorCode, query);
 
     // query_state_waiting means that we're answering a regular DNS question
     if (errorCode == kDNSServiceErr_NoError) {
-        dns_push_start(query);
-
-        const void *rdata_to_send;
-        uint32_t ttl_to_send;
-        // If kDNSServiceFlagsAdd is set, it's an add, otherwise a delete.
-    re_add:
-        if (flags & kDNSServiceFlagsAdd) {
-            rdata_to_send = rdata;
-            ttl_to_send = ttl;
-            INFO("DNS Push adding record - "
-                 "name: " PRI_S_SRP ", rrtype: " PUB_S_SRP ", rrclass: " PUB_S_SRP ", rdlen: %u, ttl: %u.",
-                 fullname, dns_rrtype_to_string(rrtype), dns_qclass_to_string(rrclass), rdlen, ttl_to_send);
-        } else {
-            // See <https://tools.ietf.org/html/rfc8765#section-6.3.1>.
-        #define TTL_TO_REMOVE_INDIVIDUAL_RECORDS    0xFFFFFFFF
-        #define TTL_TO_REMOVE_MULTIPLE_RECORDS      0xFFFFFFFE
-            if (rdlen == 0) {
-                // Remove specified RRset from a name in given class:
-                // TTL = 0xFFFFFFFE, RDLEN = 0,
-                // CLASS and TYPE specify the RRset being removed.
-                rdata_to_send = NULL;
-                ttl_to_send = TTL_TO_REMOVE_MULTIPLE_RECORDS;
-            } else {
-                // Remove an individual RR from a name:
-                // TTL = 0xFFFFFFFF,
-                // CLASS, TYPE, RDLEN, and RDATA specify the RR being removed.
+        if (send) {
+            const void *rdata_to_send;
+            uint32_t ttl_to_send;
+            // If kDNSServiceFlagsAdd is set, it's an add, otherwise a delete.
+        re_add:
+            if (flags & kDNSServiceFlagsAdd) {
                 rdata_to_send = rdata;
-                ttl_to_send = TTL_TO_REMOVE_INDIVIDUAL_RECORDS;
+                ttl_to_send = ttl;
+                INFO("[Q%d] DNS Push adding record - "
+                     "name: " PRI_S_SRP ", rrtype: " PUB_S_SRP ", rrclass: " PUB_S_SRP ", rdlen: %u, ttl: %u.",
+                     SERIAL(query), fullname, dns_rrtype_to_string(rrtype), dns_qclass_to_string(rrclass), rdlen, ttl_to_send);
+            } else {
+                // See <https://tools.ietf.org/html/rfc8765#section-6.3.1>.
+#define TTL_TO_REMOVE_INDIVIDUAL_RECORDS    0xFFFFFFFF
+#define TTL_TO_REMOVE_MULTIPLE_RECORDS      0xFFFFFFFE
+                if (rdlen == 0) {
+                    // Remove specified RRset from a name in given class:
+                    // TTL = 0xFFFFFFFE, RDLEN = 0,
+                    // CLASS and TYPE specify the RRset being removed.
+                    rdata_to_send = NULL;
+                    ttl_to_send = TTL_TO_REMOVE_MULTIPLE_RECORDS;
+                } else {
+                    // Remove an individual RR from a name:
+                    // TTL = 0xFFFFFFFF,
+                    // CLASS, TYPE, RDLEN, and RDATA specify the RR being removed.
+                    rdata_to_send = rdata;
+                    ttl_to_send = TTL_TO_REMOVE_INDIVIDUAL_RECORDS;
+                }
+                INFO("[Q%d] DNS Push removing record - "
+                     "name: " PRI_S_SRP ", rrtype: " PUB_S_SRP ", rrclass: " PUB_S_SRP ", rdlen: %u, ttl: 0x%X.",
+                     SERIAL(query), fullname, dns_rrtype_to_string(rrtype), dns_qclass_to_string(rrclass), rdlen, ttl_to_send);
             }
-            INFO("DNS Push removing record - "
-                 "name: " PRI_S_SRP ", rrtype: " PUB_S_SRP ", rrclass: " PUB_S_SRP ", rdlen: %u, ttl: 0x%X.",
-                 fullname, dns_rrtype_to_string(rrtype), dns_qclass_to_string(rrclass), rdlen, ttl_to_send);
+
+            // Do the update.
+            dp_query_add_data_to_response(query, fullname, rrtype, rrclass, rdlen, rdata_to_send,
+                                          ttl_to_send, false, false, NULL);
+
+            if (query->towire.truncated) {
+                query->towire.truncated = false;
+                query->towire.p = revert;
+                query->towire.error = 0;
+                dp_push_response(query, NULL);
+                dns_push_start(query);
+                goto re_add;
+            }
         }
-
-        // Do the update.
-        dp_query_add_data_to_response(query, fullname, rrtype, rrclass, rdlen, rdata_to_send, ttl_to_send, false);
-
-        if (query->towire.truncated) {
-            query->towire.truncated = false;
-            query->towire.p = revert;
-            query->towire.error = 0;
-            dp_push_response(query);
-            dns_push_start(query);
-            goto re_add;
-        }
-
         // If there isn't more coming, send a DNS Push notification now.
         // XXX If enough comes to fill the response, send the message.
         if (!(flags & kDNSServiceFlagsMoreComing)) {
-            dp_push_response(query);
+            dp_push_response(query, NULL);
         }
     } else if (errorCode != kDNSServiceErr_NoSuchRecord) { // Do nothing if kDNSServiceErr_NoSuchRecord is received.
-        ERROR("unexpected error code %d", errorCode);
+        ERROR("[Q%d] unexpected error code %d", SERIAL(query), errorCode);
         dnssd_query_cancel(query);
     }
 }
@@ -3161,14 +3492,27 @@
         dns64 = nat64_is_active();
     }
 #endif
-    if (!dp_query_start(query, &rcode, dns64)) {
+    bool hardwired = false;
+    if (!dp_query_start(query, &rcode, &hardwired, dns64)) {
         dso_simple_response(tracker->connection, NULL, header, rcode);
         dp_question_cache_remove_queries(query->question);
         dnssd_query_cancel(query);
     } else {
+        char nbuf[DNS_MAX_NAME_SIZE + 1];
+        dns_name_print(question->name, nbuf, sizeof(nbuf));
+        // The push subscribe can be considered a success at this point.
         dso_simple_response(tracker->connection, NULL, header, dns_rcode_noerror);
-        dp_query_reply_from_cache(query->question, query);
+        if (hardwired) {
+            INFO("[DSO%d][Q%d] hardwired response for " PRI_S_SRP " %d %d",
+                 SERIAL(dso), SERIAL(query), nbuf, question->type, question->qclass);
+            dp_push_response(query, question);
+        } else if (query->question != NULL) {
+            INFO("[DSO%d][Q%d][QU%d] replying from cache for " PRI_S_SRP " %d %d",
+                 SERIAL(dso), SERIAL(query), SERIAL(query->question), nbuf, question->type, question->qclass);
+            dp_query_reply_from_cache(query->question, query, false);
+        }
     }
+
     // dp_query_create() returned the query retained; when we added the query to the activity, we retained it again;
     // if something went wrong, the second retain was released, but whether or not something went wrong, we can now
     // safely release the initial retain.
@@ -3186,6 +3530,7 @@
     // The TLV offset should always be pointing into the message.
     unsigned offp = (unsigned)(dso->primary.payload - &header->data[0]);
     unsigned len = offp + dso->primary.length;
+    dp_tracker_t *tracker = comm->context;
 
     // Parse the name, rrtype and class.   We say there's no rdata even though there is
     // because there's no ttl and also we want the raw rdata, not parsed rdata.
@@ -3193,13 +3538,14 @@
         !dns_u16_parse(header->data, len, &offp, &rdlen))
     {
         dso_simple_response(comm, NULL, header, dns_rcode_formerr);
-        ERROR("dns_push_reconfirm: RR parse from %s failed", dso->remote_name);
+        ERROR("[DSO%d][C%d][TRK%d] RR parse from %s failed",
+              SERIAL(dso), SERIAL(comm), SERIAL(tracker), dso->remote_name);
         goto out;
     }
     if (rdlen + offp != len) {
         dso_simple_response(comm, NULL, header, dns_rcode_formerr);
-        ERROR("dns_push_reconfirm: RRdata parse from %s failed: length mismatch (%d != %d)",
-              dso->remote_name, rdlen + offp, len);
+        ERROR("[DSO%d][C%d][TRK%d] RRdata parse from %s failed: length mismatch (%d != %d)",
+              SERIAL(dso), SERIAL(comm), SERIAL(tracker), dso->remote_name, rdlen + offp, len);
         goto out;
     }
 
@@ -3207,7 +3553,7 @@
         size_t name_len = strlen(name);
         if (name_len + sizeof local_suffix > sizeof name) {
             dso_simple_response(comm, NULL, header, dns_rcode_formerr);
-            ERROR("dns_push_reconfirm: name is too long for .local suffix: %s", name);
+            ERROR("[DSO%d][C%d][TRK%d] name is too long for .local suffix: %s", SERIAL(dso), SERIAL(comm), SERIAL(tracker), name);
             goto out;
         }
         memcpy(&name[name_len], local_suffix, sizeof local_suffix);
@@ -3235,57 +3581,74 @@
 dns_push_subscription_change(const char *opcode_name, dp_tracker_t *tracker, const dns_wire_t *header, dso_state_t *dso)
 {
     // type-in-hex/class-in-hex/name-to-subscribe
-    char activity_name[DNS_MAX_NAME_SIZE_ESCAPED + 3 + 4 + 4];
+    char activity_name[5];
     dso_activity_t *activity;
 
     // The TLV offset should always be pointing into the message.
     unsigned offp = (unsigned)(dso->primary.payload - &header->data[0]);
+    unsigned len = offp + dso->primary.length;
     // Get the question
     dns_rr_t question;
+    uint16_t subscribe_xid = ntohs(header->id);
+    char nbuf[DNS_MAX_NAME_SIZE + 1];
 
     memset(&question, 0, sizeof(question));
-    if (!dns_rr_parse(&question, header->data, offp + dso->primary.length, &offp, false, false)) {
+    if (dso->primary.opcode == kDSOType_DNSPushSubscribe) {
+        if (!dns_rr_parse(&question, header->data, offp + dso->primary.length, &offp, false, false)) {
+            dso_simple_response(tracker->connection, NULL, header, dns_rcode_formerr);
+            ERROR("[DSO%d][TRK%d] RR parse for %s from %s failed", SERIAL(dso), SERIAL(tracker), dso->remote_name, opcode_name);
+            goto out;
+        }
+        dns_name_print(question.name, nbuf, sizeof(nbuf));
+    } else {
         // Unsubscribes are unidirectional, so no response can be sent
-        if (dso->primary.opcode != kDSOType_DNSPushUnsubscribe) {
+        if (!dns_u16_parse(header->data, offp + dso->primary.length, &offp, &subscribe_xid)) {
+            ERROR("unable to get subscribe xid from primary");
+            goto out;
+        }
+        const char none[] = "none";
+        memcpy(nbuf, none, sizeof(none));
+    }
+    if (offp != len) {
+        if (dso->primary.opcode == kDSOType_DNSPushSubscribe) {
             dso_simple_response(tracker->connection, NULL, header, dns_rcode_formerr);
         }
-        ERROR("RR parse for %s from %s failed", dso->remote_name, opcode_name);
+        ERROR("DNS push " PUB_S_SRP " parse from %s failed: length mismatch (%d != %d)",
+              dso->primary.opcode == kDSOType_DNSPushSubscribe ? "subscribe" : "unsubscribe",
+              dso->remote_name, offp, len);
         goto out;
     }
 
-    // Concoct an activity name.
-    snprintf(activity_name, sizeof activity_name, "%04x%04x", question.type, question.qclass);
-    if ((dp_served(question.name, &activity_name[8], (sizeof activity_name) - 8))) {
-        size_t len = strlen(activity_name);
-        if (len + sizeof local_suffix + 8 > sizeof (activity_name)) {
-            ERROR("activity name overflow for %s", activity_name);
-            goto out;
-        }
-        const int lslen = sizeof local_suffix;
-        strncpy(&activity_name[len], local_suffix, lslen);
-    } else {
-        dns_name_print(question.name, &activity_name[8], (sizeof activity_name) - 8);
-    }
+    // Concoct an activity name. The subscribe transaction ID is required to be unique and is used by the
+    // protocol to identify the subscription, so we can just use that.
+    snprintf(activity_name, sizeof(activity_name), "%04x", subscribe_xid);
 
     activity = dso_find_activity(dso, activity_name, push_subscription_activity_type, NULL);
     if (activity == NULL) {
         // Unsubscribe with no activity means no work to do; just return noerror.
         if (dso->primary.opcode != kDSOType_DNSPushSubscribe) {
-            ERROR("dso_message: %s for %s when no subscription exists.", opcode_name, activity_name);
-            if (dso->primary.opcode == kDSOType_DNSPushReconfirm) {
-                dso_simple_response(tracker->connection, NULL, header, dns_rcode_noerror);
-            }
+            ERROR("[DSO%d][TRK%d] " PUB_S_SRP " for " PRI_S_SRP " (" PUB_S_SRP ") when no subscription exists.",
+                  SERIAL(dso), SERIAL(tracker), opcode_name, nbuf, activity_name);
         } else {
+            INFO("[DSO%d][TRK%d] " PUB_S_SRP " for " PRI_S_SRP " (" PUB_S_SRP ") type %d.",
+                 SERIAL(dso), SERIAL(tracker), opcode_name, nbuf, activity_name, question.type);
             // In this case we have a push subscribe for which no subscription exists, which means we can do it.
             dns_push_subscribe(tracker, header, dso, &question, activity_name, opcode_name);
         }
     } else {
-        // Subscribe with a matching activity means no work to do; just return noerror.
+        // We should never get two subscribes with the same transaction id.
         if (dso->primary.opcode == kDSOType_DNSPushSubscribe) {
-            dso_simple_response(tracker->connection, NULL, header, dns_rcode_noerror);
+            ERROR("[DSO%d][TRK%d] " PUB_S_SRP " for " PRI_S_SRP " (" PUB_S_SRP ") "
+                  "xid %d when subscription already exists.", SERIAL(dso), SERIAL(tracker),
+                  opcode_name, nbuf, activity_name, subscribe_xid);
+            dso_simple_response(tracker->connection, NULL, header, dns_rcode_refused);
         }
         // Otherwise cancel the subscription.
         else {
+            dnssd_query_t *query = activity->context;
+            char *question_name = query->question != NULL ? query->question->name : nbuf;
+            INFO("[DSO%d][TRK%d] " PUB_S_SRP " for " PRI_S_SRP " (" PUB_S_SRP ") type %d.",
+                 SERIAL(dso), SERIAL(tracker), opcode_name, question_name, activity_name, question.type);
             dns_push_unsubscribe(activity);
         }
     }
@@ -3299,9 +3662,10 @@
 {
     if (num_push_sessions == MAX_DSO_CONNECTIONS) {
         // We are too busy. Return a retry-delay response.
-        INFO("no more DNS Push connections allowed--sending retry-delay: %d", num_push_sessions);
+        INFO("[TRK%d] no more DNS Push connections allowed--sending retry-delay: %d", SERIAL(tracker), num_push_sessions);
         dso_retry_delay_response(tracker->connection, message, &message->wire, dns_rcode_servfail, BUSY_RETRY_DELAY_MS);
 
+        num_push_sessions_dropped_for_load++;
         // Cancel the connection after five seconds
         dp_tracker_idle_after(tracker, 5, NULL);
         return true;
@@ -3309,7 +3673,7 @@
 
     // Count this as a DSO connection.
     (num_push_sessions)++;
-    INFO("new DNS Push connection, count is now %d", num_push_sessions);
+    INFO("[TRK%d] new DNS Push connection, count is now %d", SERIAL(tracker), num_push_sessions);
 
     tracker->session_type = session_type;
     return false;
@@ -3342,7 +3706,7 @@
         break;
 
     case kDSOType_DNSPushUpdate:
-        INFO("bogus push update message %d", dso->primary.opcode);
+        INFO("[DSO%d][TRK%d] bogus push update message %d", SERIAL(dso), SERIAL(tracker), dso->primary.opcode);
         dso_state_cancel(dso);
         break;
 
@@ -3350,80 +3714,142 @@
     case kDSOType_SRPLSession:
         if (dso->activities != NULL) {
             dso_state_cancel(dso);
-            ERROR(PRI_S_SRP ": SRP Replication session start received on a connection that is already doing DNS Push.",
-                  tracker->connection->name);
+            ERROR("[DSO%d][TRK%d][C%d] " PRI_S_SRP ": SRP Replication session start received on a connection that is already doing DNS Push.",
+                  SERIAL(dso), SERIAL(tracker), SERIAL(tracker->connection), tracker->connection->name);
             return;
         }
+#ifdef SRP_TEST_SERVER
+        srpl_dso_server_message(tracker->connection, message, dso, (srp_server_t*)tracker->connection->srp_server);
+#else
         srpl_dso_server_message(tracker->connection, message, dso, srp_servers);
+#endif
         break;
 #endif
 
     default:
-        INFO("unexpected primary TLV %d", dso->primary.opcode);
+        INFO("[DSO%d][TRK%d] unexpected primary TLV %d", SERIAL(dso), SERIAL(tracker), dso->primary.opcode);
         dso_simple_response(tracker->connection, NULL, &message->wire, dns_rcode_dsotypeni);
         break;
     }
     // XXX free the message if we didn't consume it.
+#ifdef SRP_TEST_SERVER
+    if (srp_test_dso_message_finished != NULL) {
+        srp_test_dso_message_finished(srp_test_tls_listener_context, message, dso);
+    }
+#endif
 }
 
-static void dns_push_callback(void *context, void *event_context,
-                              dso_state_t *dso, dso_event_type_t eventType)
+static void
+dp_keepalive_response_send(dso_keepalive_context_t *keepalive_event, dso_state_t *dso)
 {
+    uint8_t dsobuf[SRPL_KEEPALIVE_MESSAGE_LENGTH];
+    dns_towire_state_t towire;
+    struct iovec iov;
+    uint16_t *p_dso_length;
+    dso_message_t state;
+
+    if (dso->transport == NULL) {
+        ERROR("dso state " PRI_S_SRP " has no transport", dso->remote_name);
+        return;
+    }
+
+    memset(&towire, 0, sizeof(towire));
+    towire.p = &dsobuf[DNS_HEADER_SIZE];
+    towire.lim = towire.p + (sizeof(dsobuf) - DNS_HEADER_SIZE);
+    towire.message = (dns_wire_t *)dsobuf;
+    towire.p_rdlength = NULL;
+    towire.p_opt = NULL;
+    p_dso_length = NULL;
+
+    dso_make_message(&state, dsobuf, sizeof(dsobuf), dso, false /* unidirectional */, true /* response */,
+                     keepalive_event->xid, dns_rcode_noerror, dso->transport);
+    dns_u16_to_wire(&towire, kDSOType_Keepalive);
+    dns_rdlength_begin(&towire);
+    dns_u32_to_wire(&towire, keepalive_event->inactivity_timeout); // Idle timeout (we are never idle)
+    dns_u32_to_wire(&towire, keepalive_event->keepalive_interval); // Keepalive timeout
+    dns_rdlength_end(&towire);
+    if (towire.error) {
+        ERROR("ran out of message space at " PUB_S_SRP ", :%d", __FILE__, towire.line);
+        return;
+    }
+    memset(&iov, 0, sizeof(iov));
+    iov.iov_len = towire.p - dsobuf;
+    iov.iov_base = dsobuf;
+    if (!ioloop_send_message(dso->transport, NULL, &iov, 1)) {
+        INFO("send failed");
+        return;
+    }
+
+    INFO("sent %zd byte response Keepalive, xid %02x%02x (was %04x), to " PRI_S_SRP,
+         iov.iov_len, dsobuf[0], dsobuf[1], keepalive_event->xid, dso->transport->name);
+}
+
+static void
+dns_push_callback(void *context, void *event_context, dso_state_t *dso, dso_event_type_t eventType)
+{
+    dso_keepalive_context_t *keepalive_context;
     message_t *message;
     switch(eventType)
     {
     case kDSOEventType_DNSMessage:
         // We shouldn't get here because we already handled any DNS messages
         message = event_context;
-        INFO("DNS Message (opcode=%d) received from " PRI_S_SRP, dns_opcode_get(&message->wire),
+        INFO("[DSO%d] DNS Message (opcode=%d) received from " PRI_S_SRP, SERIAL(dso), dns_opcode_get(&message->wire),
              dso->remote_name);
         break;
     case kDSOEventType_DNSResponse:
         // We shouldn't get here because we already handled any DNS messages
         message = event_context;
-        INFO("DNS Response (opcode=%d) received from " PRI_S_SRP, dns_opcode_get(&message->wire),
+        INFO("[DSO%d] DNS Response (opcode=%d) received from " PRI_S_SRP, SERIAL(dso), dns_opcode_get(&message->wire),
              dso->remote_name);
         break;
     case kDSOEventType_DSOMessage:
-        INFO("DSO Message (Primary TLV=%d) received from " PRI_S_SRP,
-               dso->primary.opcode, dso->remote_name);
+        INFO("[DSO%d] DSO Message (Primary TLV=%d) received from " PRI_S_SRP,
+             SERIAL(dso), dso->primary.opcode, dso->remote_name);
         message = event_context;
         dso_message((dp_tracker_t *)context, message, dso);
         break;
     case kDSOEventType_DSOResponse:
-        INFO("DSO Response (Primary TLV=%d) received from " PRI_S_SRP,
-               dso->primary.opcode, dso->remote_name);
+        INFO("[DSO%d] DSO Response (Primary TLV=%d) received from " PRI_S_SRP,
+             SERIAL(dso), dso->primary.opcode, dso->remote_name);
         break;
 
     case kDSOEventType_Finalize:
-        INFO("Finalize");
+        INFO("[DSO%d] Finalize", SERIAL(dso));
         break;
 
     case kDSOEventType_Connected:
-        INFO("Connected to " PRI_S_SRP, dso->remote_name);
+        INFO("[DSO%d] Connected to " PRI_S_SRP, SERIAL(dso), dso->remote_name);
         break;
 
     case kDSOEventType_ConnectFailed:
-        INFO("Connection to " PRI_S_SRP " failed", dso->remote_name);
+        INFO("[DSO%d] Connection to " PRI_S_SRP " failed", SERIAL(dso), dso->remote_name);
         break;
 
     case kDSOEventType_Disconnected:
-        INFO("Connection to " PRI_S_SRP " disconnected", dso->remote_name);
+        INFO("[DSO%d] Connection to " PRI_S_SRP " disconnected", SERIAL(dso), dso->remote_name);
         break;
     case kDSOEventType_ShouldReconnect:
-        INFO("Connection to " PRI_S_SRP " should reconnect (not for a server)", dso->remote_name);
+        INFO("[DSO%d] Connection to " PRI_S_SRP " should reconnect (not for a server)", SERIAL(dso), dso->remote_name);
         break;
     case kDSOEventType_Inactive:
-        INFO("Inactivity timer went off, closing connection.");
+        INFO("[DSO%d] Inactivity timer went off, closing connection.", SERIAL(dso));
         break;
     case kDSOEventType_Keepalive:
-        INFO("should send a keepalive now.");
+        INFO("[DSO%d] should send a keepalive now.", SERIAL(dso));
         break;
     case kDSOEventType_KeepaliveRcvd:
-        INFO("keepalive received.");
+        keepalive_context = event_context;
+        keepalive_context->send_response = false;
+        INFO("[DSO%d] " PRI_S_SRP ": keepalive received, xid %04x.", SERIAL(dso), dso->transport->name, keepalive_context->xid);
+
+        // If we are the server, we have to send a response to the keepalive.
+        if (dso->is_server) {
+            dp_keepalive_response_send(keepalive_context, dso);
+        }
         break;
     case kDSOEventType_RetryDelay:
-        INFO("keepalive received.");
+        INFO("[DSO%d] keepalive received.", SERIAL(dso));
         break;
     }
 }
@@ -3435,22 +3861,17 @@
 
     // Limit outstanding queries if we don't have shared connection support
     if (dp_num_outstanding_queries >= 256) {
+        num_queries_dropped_for_load++;
         dso_simple_response(tracker->connection, message, &message->wire, dns_rcode_servfail);
-        ERROR("[QID %x] dropping query because there are too many", ntohs(message->wire.id));
+        ERROR("[TRK%d][QID %x] dropping query because there are too many", SERIAL(tracker), ntohs(message->wire.id));
         return false;
     }
 
-    // We do not support queries in the ".local" domain
-    if (is_in_local_domain(question->name)) {
-        dso_simple_response(tracker->connection, message, &message->wire, dns_rcode_refused);
-        ERROR("[QID %x] dropping query to local domain", ntohs(message->wire.id));
-        return false;
-    }
 
     dnssd_query_t *query = dp_query_create(tracker, question, message, NULL, &rcode);
     const char *failnote = NULL;
     if (!query) {
-        ERROR("[QID %x] query create failed", ntohs(message->wire.id));
+        ERROR("[TRK%d][QID %x] query create failed", SERIAL(tracker), ntohs(message->wire.id));
         dso_simple_response(tracker->connection, message, &message->wire, rcode);
         return false;
     }
@@ -3471,7 +3892,7 @@
     TOWIRE_CHECK("TYPE", &query->towire, dns_u16_to_wire(&query->towire, question->type));    // TYPE
     TOWIRE_CHECK("CLASS", &query->towire, dns_u16_to_wire(&query->towire, question->qclass));  // CLASS
     if (failnote != NULL) {
-        ERROR("[QID %x] failure encoding question: " PUB_S_SRP, ntohs(message->wire.id), failnote);
+        ERROR("[TRK%d][QID %x] failure encoding question: " PUB_S_SRP, SERIAL(tracker), ntohs(message->wire.id), failnote);
         goto fail;
     }
 
@@ -3488,17 +3909,24 @@
     }
 #endif
     dp_query_track(tracker, query);
-    if (dp_query_start(query, &rcode, dns64)) {
+    bool hardwired = false;
+    if (dp_query_start(query, &rcode, &hardwired, dns64)) {
         // If query->question isn't NULL, we need to reply from cache
-        if (query->question != NULL) {
-            INFO("replying from cache");
-            dp_query_reply_from_cache(query->question, query);
+        if (hardwired) {
+            INFO("[Q%d][TRK%d] hardwired reply", SERIAL(query), SERIAL(tracker));
+            dp_query_send_dns_response(query, "hardwired");
+            dp_question_cache_remove_queries(query->question);
+            RELEASE_HERE(query->question, question);
+            query->question = NULL;
+        } else if (query->question != NULL) {
+            INFO("[Q%d][TRK%d] replying from cache", SERIAL(query), SERIAL(tracker));
+            dp_query_reply_from_cache(query->question, query, false);
             dp_question_cache_remove_queries(query->question);
         } else {
-            INFO("not replying from cache");
+            INFO("[Q%d][TRK%d] not replying from cache", SERIAL(query), SERIAL(tracker));
         }
     } else {
-        ERROR("[QID %x] query start failed", ntohs(message->wire.id));
+        INFO("[Q%d][TRK%d][QID %x] query start failed", SERIAL(query), SERIAL(tracker), ntohs(message->wire.id));
     fail:
         dso_simple_response(tracker->connection, message, &message->wire, rcode);
         query->satisfied = true;
@@ -3555,10 +3983,11 @@
     if (tracker == NULL) {
         tracker = calloc(1, sizeof(*tracker));
         if (tracker == NULL) {
-            ERROR(PRI_S_SRP ": no memory for a connection tracker object!", comm->name);
+            ERROR("[C%d] " PRI_S_SRP ": no memory for a connection tracker object!", SERIAL(comm), comm->name);
             goto fail;
         }
         tracker->connection = comm;
+        tracker->serial = ++cur_tracker_serial;
         ioloop_comm_retain(tracker->connection);
         if (comm->tcp_stream) {
             ioloop_comm_context_set(comm, tracker, dp_tracker_context_release);
@@ -3572,7 +4001,7 @@
 
     // Drop incoming responses--we're a server, so we only accept queries.
     if (dns_qr_get(&message->wire) == dns_qr_response) {
-        INFO("dropping unexpected response");
+        INFO("[TRK%d][C%d] " PRI_S_SRP ": dropping unexpected response", SERIAL(tracker), SERIAL(comm), comm->name);
         goto fail;
     }
 
@@ -3580,7 +4009,7 @@
     switch(dns_opcode_get(&message->wire)) {
     case dns_opcode_dso:
         if (!comm->tcp_stream) {
-            ERROR("DSO message received on non-tcp socket %s", comm->name);
+            ERROR("[TRK%d][C%d] " PRI_S_SRP ": DSO message received on non-tcp socket.", SERIAL(tracker), SERIAL(comm), comm->name);
             dso_simple_response(comm, message, &message->wire, dns_rcode_notimp);
             goto fail;
         }
@@ -3589,7 +4018,7 @@
             tracker->dso = dso_state_create(true, 2, comm->name, dns_push_callback, tracker,
                                             dp_tracker_dso_state_change, comm);
             if (!tracker->dso) {
-                ERROR("Unable to create a dso context for %s", comm->name);
+                ERROR("[TRK%d][C%d] " PRI_S_SRP ": Unable to create a dso context.", SERIAL(tracker), SERIAL(comm), comm->name);
                 dso_simple_response(comm, message, &message->wire, dns_rcode_servfail);
                 goto fail;
             }
@@ -3607,7 +4036,7 @@
         for (int i = 0; i < num_questions; i++) {
             memset(&question, 0, sizeof(question));
             if (!dns_rr_parse(&question, message->wire.data, message->length - DNS_HEADER_SIZE, &offset, false, false)) {
-                INFO("rr parse failed");
+                ERROR("[TRK%d][C%d] " PRI_S_SRP ": rr parse failed.", SERIAL(tracker), SERIAL(comm), comm->name);
                 dso_simple_response(comm, message, &message->wire, dns_rcode_formerr);
                 goto fail;
             }
@@ -3654,15 +4083,28 @@
     }
 }
 
+void
+dns_proxy_input_for_server(comm_t *comm, srp_server_t *server_state, message_t *message, void *context)
+{
+    char buf[INET6_ADDRSTRLEN];
+    const char *remote_name = buf;
+    if (comm->tcp_stream) {
+        remote_name = comm->name;
+    } else {
+        IOLOOP_NTOP(&message->src, buf);
+    }
+    dp_tracker_t *tracker = comm->context;
+    INFO("[C%d][TRK%d][QID %x] Received a new DNS message - src: " PRI_S_SRP ", message length: %u bytes.",
+         SERIAL(comm), SERIAL(tracker), ntohs(message->wire.id), remote_name, message->length);
+
+
+    dnssd_proxy_dns_evaluate(comm, message, context);
+}
+
 static void
 dns_proxy_input(comm_t *comm, message_t *message, void *context)
 {
-    char buf[INET6_ADDRSTRLEN];
-    IOLOOP_NTOP(&comm->address, buf);
-    INFO("[QID %x] Received a new DNS message - src: " PRI_S_SRP ", message length: %u bytes.",
-         ntohs(message->wire.id), buf, message->length);
-
-    dnssd_proxy_dns_evaluate(comm, message, context);
+    dns_proxy_input_for_server(comm, srp_servers, message, context);
 }
 
 // usage is only called when we are building standalone dnssd-proxy, not the combined one.
@@ -3680,7 +4122,7 @@
 static void UNUSED
 connected(comm_t *comm)
 {
-    INFO("connection from " PRI_S_SRP, comm->name);
+    INFO("[C%d] connection from " PRI_S_SRP, SERIAL(comm), comm->name);
     return;
 }
 
@@ -3720,6 +4162,7 @@
     return sdt;
 }
 
+#if STUB_ROUTER
 static served_domain_t *NULLABLE
 find_served_domain(const char *const NONNULL domain)
 {
@@ -3732,15 +4175,16 @@
 
     return current;
 }
+#endif
 
 // served domain can only go away when combined with srp-mdns-proxy and interface going up and down.
 #if SRP_FEATURE_DYNAMIC_CONFIGURATION
 static void
-delete_served_domain(served_domain_t *const served_domain)
+served_domain_free(served_domain_t *const served_domain)
 {
     INFO("served domain removed - domain name: " PRI_S_SRP, served_domain->domain);
 
-    // free struct interface *NULLABLE interface
+   // free struct interface *NULLABLE interface
     if (served_domain->interface != NULL) {
         interface_addr_t *current = served_domain->interface->addresses;
         interface_addr_t *next;
@@ -3772,15 +4216,26 @@
     // free char *NONNULL domain_ld;
     free(served_domain->domain_ld);
 
+#ifdef SRP_TEST_SERVER
+    last_freed_domain = served_domain;
+#endif
+
     // free served_domain_t *
     free(served_domain);
 }
 
-bool
+static void
+delete_served_domain(served_domain_t *const served_domain)
+{
+    if (served_domain->questions == NULL) {
+        served_domain_free(served_domain);
+    }
+}
+
+#if STUB_ROUTER
+served_domain_t *
 delete_served_domain_by_interface_name(const char *const NONNULL interface_name)
 {
-    bool deleted = false;
-
     served_domain_t *current;
     served_domain_t *prev = NULL;
     for(current= served_domains; current != NULL; prev = current, current = current->next) {
@@ -3808,20 +4263,21 @@
         }
 
         delete_served_domain(current);
-        deleted = true;
         break;
     }
 
-    return deleted;
+    return current;
 }
+#endif // STUB_ROUTER
 #endif // SRP_FEATURE_DYNAMIC_CONFIGURATION
 
 // Dynamic interface detection...
 // This is called whenever a new interface address is encountered.
 
 void
-dnssd_proxy_ifaddr_callback(void *UNUSED context, const char *name, const addr_t *address, const addr_t *mask,
-    uint32_t UNUSED flags, enum interface_address_change event_type)
+dnssd_proxy_ifaddr_callback(srp_server_t *UNUSED server_state, void *UNUSED context, const char *name,
+                            const addr_t *address, const addr_t *mask, uint32_t UNUSED flags,
+                            enum interface_address_change event_type)
 {
 #if SRP_FEATURE_DYNAMIC_CONFIGURATION
     bool is_new_interface = true;
@@ -3906,7 +4362,7 @@
 #if SRP_FEATURE_DYNAMIC_CONFIGURATION
     if (event_type == interface_address_added && is_new_interface) {
         served_domain_t *const new_served_domain = add_new_served_domain_with_interface(name, address, mask);
-        verify_action(new_served_domain != NULL,
+        require_action_quiet(new_served_domain != NULL, exit,
             ERROR("failed to add new served domain ""- interface name: " PUB_S_SRP, name));
 
         bool hardwired_set = dnssd_hardwired_setup_for_served_domain(new_served_domain);
@@ -3919,8 +4375,10 @@
     }
 #endif // SRP_FEATURE_DYNAMIC_CONFIGURATION
 
+#if STUB_ROUTER
     // Added or removed address will possibly need hardwired response to be updated.
     dnssd_hardwired_process_addr_change(address, mask, event_type == interface_address_added);
+#endif
 
 exit:
     return;
@@ -3967,11 +4425,11 @@
         return false;
     }
     if (!strcmp(hunks[0], "udp-port")) {
-        udp_port = port;
+        dnssd_proxy_udp_port = port;
     } else if (!strcmp(hunks[0], "tcp-port")) {
-        tcp_port = port;
+        dnssd_proxy_tcp_port = port;
     } else if (!strcmp(hunks[0], "tls-port")) {
-        tls_port = port;
+        dnssd_proxy_tls_port = port;
     }
     return true;
 }
@@ -4090,9 +4548,46 @@
 
 static wakeup_t *tls_listener_wakeup;
 static int tls_listener_index;
-static void dnssd_tls_listener_restart(void *NULLABLE context);
+static void dnssd_tls_listener_restart(comm_t *NONNULL listener, void *NULLABLE context);
 
-static void dnssd_tls_listener_listen(void *UNUSED context)
+static void dnssd_tls_key_change_notification_send(void)
+{
+    static int dnssd_tls_change_notification_token = NOTIFY_TOKEN_INVALID;
+
+    if (dnssd_tls_change_notification_token == NOTIFY_TOKEN_INVALID) {
+        uint32_t notifyStatus = notify_register_check(kDNSSDAdvertisingProxyTLSKeyUpdateNotification,
+                                                      &dnssd_tls_change_notification_token);
+        if (notifyStatus != NOTIFY_STATUS_OK) {
+            dnssd_tls_change_notification_token = NOTIFY_TOKEN_INVALID;
+            ERROR("notify_register_check(%s) failed with %u", kDNSSDAdvertisingProxyTLSKeyUpdateNotification, notifyStatus);
+            return;
+        }
+    }
+
+    if (dnssd_tls_change_notification_token != NOTIFY_TOKEN_INVALID) {
+        uint32_t notifyStatus = notify_post(kDNSSDAdvertisingProxyTLSKeyUpdateNotification);
+        if (notifyStatus != NOTIFY_STATUS_OK) {
+            ERROR("notify_post(%s) %u", kDNSSDAdvertisingProxyTLSKeyUpdateNotification, notifyStatus);
+            notify_cancel(dnssd_tls_change_notification_token);
+            dnssd_tls_change_notification_token = NOTIFY_TOKEN_INVALID;
+        }
+    }
+}
+
+static void
+dnssd_tls_listener_ready(void *UNUSED context, uint16_t port)
+{
+#ifdef SRP_TEST_SERVER
+    if (srp_test_dnssd_tls_listener_ready != NULL) {
+        srp_test_dnssd_tls_listener_ready(srp_test_tls_listener_context, port);
+    }
+#else
+    (void)context;
+    (void)port;
+#endif
+}
+
+static void dnssd_tls_listener_listen(void *context, bool init_daemon)
 {
     addr_t addr;
     INFO("starting DoT listener");
@@ -4101,33 +4596,43 @@
 #ifndef NOT_HAVE_SA_LEN
     addr.sa.sa_len = sizeof(addr.sin6);
 #endif
-    addr.sin6.sin6_port = htons(tls_port);
+    addr.sin6.sin6_port = htons(dnssd_proxy_tls_port);
 #ifndef EXCLUDE_TLS
-    listener[tls_listener_index] = ioloop_listener_create(true, true, NULL, 0, &addr, NULL, "DNS Push Listener",
-                                                          dns_proxy_input, NULL, dnssd_tls_listener_restart, NULL,
-                                                          NULL, srp_tls_configure, NULL);
+    dnssd_proxy_listeners[tls_listener_index] =
+            ioloop_listener_create(true, true, init_daemon, NULL, 0, &addr, NULL, "DNS over TLS",
+                                   dns_proxy_input, NULL, dnssd_tls_listener_restart, dnssd_tls_listener_ready,
+                                   NULL, srp_tls_configure, 0, context);
 #else
-    listener[tls_listener_index] = ioloop_listener_create(true, true, NULL, 0, &addr, NULL, "DNS Push Listener",
-                                                          dns_proxy_input, NULL, dnssd_tls_listener_restart, NULL,
-                                                          NULL, NULL, NULL);
+    dnssd_proxy_listeners[tls_listener_index] =
+        ioloop_listener_create(true, true, init_daemon, NULL, 0, &addr, NULL, "DNS over TLS",
+                               dns_proxy_input, NULL, dnssd_tls_listener_restart, dnssd_tls_listener_ready,
+                               NULL, NULL, 0, context);
 #endif
-    if (listener[tls_listener_index] == NULL) {
+    if (dnssd_proxy_listeners[tls_listener_index] == NULL) {
         ERROR("DNS Push listener: fail.");
         goto exit;
     }
 
+    // Notify about intial key update
+    dnssd_tls_key_change_notification_send();
+
     // Schedule a wake up timer to rotate the expired TLS certificate.
-    schedule_tls_certificate_rotation(&tls_listener_wakeup, listener[tls_listener_index]);
+    schedule_tls_certificate_rotation(&tls_listener_wakeup, dnssd_proxy_listeners[tls_listener_index]);
 exit:
     return;
 }
 
-static void
-dnssd_tls_listener_restart(void *UNUSED context)
+static void dnssd_tls_listener_relisten(void *context)
 {
-    const bool doing_rotation = listener[tls_listener_index]->tls_rotation_ready;
-    ioloop_listener_release(listener[tls_listener_index]);
-    listener[tls_listener_index] = NULL;
+    dnssd_tls_listener_listen(context, false);
+}
+
+static void
+dnssd_tls_listener_restart(comm_t *UNUSED in_listener, void *context)
+{
+    const bool doing_rotation = dnssd_proxy_listeners[tls_listener_index]->tls_rotation_ready;
+    ioloop_listener_release(dnssd_proxy_listeners[tls_listener_index]);
+    dnssd_proxy_listeners[tls_listener_index] = NULL;
 
     if (doing_rotation) {
         const bool succeeded = srp_tls_init();
@@ -4136,7 +4641,9 @@
             return;
         }
 
-        dnssd_tls_listener_listen(NULL);
+        // Send TLS key update notification
+        dnssd_tls_key_change_notification_send();
+        dnssd_tls_listener_listen(NULL, false);
     } else {
         INFO("Creation of TLS listener failed; reattempting in 10s.");
 
@@ -4147,15 +4654,15 @@
                 return;
             }
         }
-        ioloop_add_wake_event(tls_listener_wakeup, NULL, dnssd_tls_listener_listen, NULL, 10 * MSEC_PER_SEC);
+        ioloop_add_wake_event(tls_listener_wakeup, context, dnssd_tls_listener_relisten, NULL, 10 * MSEC_PER_SEC);
     }
 }
 
 static void
 dnssd_push_setup(void)
 {
-    tls_listener_index = num_listeners++;
-    dnssd_tls_listener_listen(NULL);
+    tls_listener_index = dnssd_proxy_num_listeners++;
+    dnssd_tls_listener_listen(NULL, true);
 
     // Only set hardwired response when dynamic configuration is enabled.  Dynamic configuration
     // sets up hardwired response when new address of the interface is added.
@@ -4269,12 +4776,12 @@
 
     // If we were able to generate a cert, we can start DNS Push service and start advertising it.
     if (finished_okay("Certificate signing", status, error)) {
-        int i = num_listeners;
+        int i = dnssd_proxy_num_listeners;
 
         dnssd_push_setup();
 
-        for (; i < num_listeners; i++) {
-            INFO("Started " PUB_S_SRP, listener[i]->name);
+        for (; i < dnssd_proxy_num_listeners; i++) {
+            INFO("Started " PUB_S_SRP, dnssd_proxy_listeners[i]->name);
         }
     }
 }
@@ -4305,7 +4812,10 @@
     dp_interface_t *new_interface = NULL;
     served_domain_t *served_domain = NULL;
     bool local_only_interface = !strcmp(LOCAL_ONLY_PSEUDO_INTERFACE, name);
-    bool locally_served_interface = !strcmp(ALL_LOCALS_PSEUDO_INTERFACE, name);
+    bool locally_served_interface = !local_only_interface && !strcmp(ALL_LOCALS_PSEUDO_INTERFACE, name);
+#if SRP_FEATURE_LOCAL_DISCOVERY
+    bool infrastructure_interface = !locally_served_interface && !strcmp(INFRASTRUCTURE_PSEUDO_INTERFACE, name);
+#endif
     bool succeeded;
 
     new_interface = calloc(1, sizeof(*new_interface));
@@ -4320,6 +4830,10 @@
         new_interface->ifindex = kDNSServiceInterfaceIndexLocalOnly;
     } else if (locally_served_interface) {
         new_interface->ifindex = kDNSServiceInterfaceIndexAny;
+#if SRP_FEATURE_LOCAL_DISCOVERY
+    } else if (infrastructure_interface) {
+        new_interface->ifindex = kDNSServiceInterfaceIndexInfra;
+#endif
     } else {
         new_interface->ifindex = if_nametoindex(name);
     }
@@ -4338,13 +4852,20 @@
     }
 
     char *per_interface_served_domain;
+#if STUB_ROUTER
     char served_domain_buffer[DNS_MAX_NAME_SIZE];
+#endif
     if (local_only_interface) {
         // All queries sent to <Thread ID>.thread.home.arpa. will only be proxied to local only interface.
         per_interface_served_domain = THREAD_DOMAIN_WITH_ID;
     } else if (locally_served_interface) {
         per_interface_served_domain = DEFAULT_SERVICE_ARPA_DOMAIN;
+#if SRP_FEATURE_LOCAL_DISCOVERY
+    } else if (infrastructure_interface) {
+        per_interface_served_domain = DOT_LOCAL_DOMAIN;
+#endif
     } else {
+#if STUB_ROUTER
         int bytes_written = snprintf(served_domain_buffer, sizeof(served_domain_buffer),
             "%s-%s." HOME_NET_DOMAIN, local_host_name, name);
         require_action_quiet(bytes_written > 0 && (size_t)bytes_written < sizeof(served_domain_buffer), exit,
@@ -4353,6 +4874,11 @@
                 ", name buffer size: %lu", my_name, name, sizeof(served_domain_buffer))
         );
         per_interface_served_domain = served_domain_buffer;
+#else
+        ERROR("unexpected served domain " PRI_S_SRP, name);
+        succeeded = false;
+        goto exit;
+#endif
     }
 
     served_domain = new_served_domain(new_interface, per_interface_served_domain);
@@ -4375,8 +4901,6 @@
             if (new_interface->name != NULL) {
                 free(new_interface->name);
             }
-        }
-        if (new_interface != NULL) {
             free(new_interface);
         }
     }
@@ -4449,18 +4973,12 @@
     new_if_addr->mask = *mask;
     new_if_addr->next = NULL;
 
-    interface_addr_t *prev;
-    interface_addr_t *current;
+    interface_addr_t **ap;
 
-    for (prev = NULL, current = interface->addresses; current != NULL; prev = current, current = current->next)
+    for (ap = &interface->addresses; *ap != NULL; ap = &(*ap)->next)
         ;
 
-    if (prev != NULL) {
-        prev->next = new_if_addr;
-    } else {
-        interface->addresses = new_if_addr;
-    }
-
+    *ap = new_if_addr;
     succeeded = true;;
 exit:
     return succeeded;
@@ -4472,25 +4990,21 @@
 {
     bool succeeded;
     interface_addr_t addr_to_remove = {NULL, *address, *mask};
-    interface_addr_t *prev;
+    interface_addr_t **ap;
     interface_addr_t *current;
 
-    for (prev = NULL, current = interface->addresses; current != NULL; prev = current, current = current->next) {
-        if (interface_addr_t_equal(current, &addr_to_remove)) {
+    for (ap = &interface->addresses; *ap != NULL; ap = &(*ap)->next) {
+        if (interface_addr_t_equal(*ap, &addr_to_remove)) {
             break;
         }
     }
-    if (current == NULL) {
+    if (*ap == NULL) {
         INFO("address not found in the interface address list - interface name: " PUB_S_SRP, interface->name);
         succeeded = false;
         goto exit;
     }
-
-    if (prev != NULL) {
-        prev->next = current->next;
-    } else {
-        interface->addresses = NULL;
-    }
+    current = *ap;
+    *ap = current->next;
     free(current);
 
     succeeded = true;
@@ -4530,6 +5044,7 @@
     towire_ptr->p = wire_ptr->data;
 }
 
+#if STUB_ROUTER
 static bool
 string_ends_with(const char *const NONNULL str, const char *const NONNULL suffix)
 {
@@ -4551,8 +5066,10 @@
 exit:
     return ret;
 }
+#endif
 
 #if SRP_FEATURE_DYNAMIC_CONFIGURATION
+#if STUB_ROUTER
 static bool
 served_domain_change_domain_name(void)
 {
@@ -4574,14 +5091,14 @@
         char *new_served_domain_name;
         char new_served_domain_buff[DNS_MAX_NAME_SIZE];
 
-        if (current->interface != NULL) { // <local host name>-<interface name>.home.arpa.
+        if (0) {
+        } else if (current->interface != NULL) { // <local host name>-<interface name>.home.arpa.
             const dp_interface_t *const interface = current->interface;
             int bytes_written = snprintf(new_served_domain_buff, sizeof(new_served_domain_buff),
                 "%s-%s." HOME_NET_DOMAIN, local_host_name, interface->name);
             require_action_quiet(bytes_written > 0 && (size_t)bytes_written < sizeof(new_served_domain_buff), exit,
                 succeeded = false; ERROR("snprintf failed"));
             new_served_domain_name = new_served_domain_buff;
-
         } else { // <local host name>.home.arpa.
             int bytes_written = snprintf(new_served_domain_buff, sizeof(new_served_domain_buff),
                 "%s." HOME_NET_DOMAIN, local_host_name);
@@ -4620,6 +5137,7 @@
 exit:
     return succeeded;
 }
+#endif // STUB_ROUTER
 
 static bool
 served_domain_process_name_change(void)
@@ -4629,9 +5147,11 @@
     // Deletes all hardwired response set in the served domain.
     dnssd_hardwired_clear();
 
+#if STUB_ROUTER
     // Since local host name changes, we need to reflect the change in the served domain name.
     succeeded = served_domain_change_domain_name();
     require_action_quiet(succeeded, exit, ERROR("served_domain_change_domain_name failed"));
+#endif
 
     // Re-set the hardwired response
     dnssd_hardwired_setup();
@@ -4640,11 +5160,30 @@
     dnssd_hardwired_push_setup();
 
     succeeded = true;
+#if STUB_ROUTER
 exit:
+#endif
     return succeeded;
 }
 
 static bool
+initialize_uuid_name(srp_server_t *UNUSED server_state)
+{
+    char *s;
+    uint64_t uuid = srp_random64();
+    static const char letters[] = "0123456789abcdefghijklmnopqrstuvwxyz";
+    static int letlen = sizeof(letters) - 1;
+    s = uuid_name;
+    *s++ = 'u'; // So that it always starts with a letter.
+    while (s < uuid_name + sizeof(uuid_name) - 1 && uuid != 0) {
+        *s++ = letters[uuid % letlen];
+        uuid /= letlen;
+    }
+    *s++ = 0;
+    return true;
+}
+
+static bool
 update_my_name(CFStringRef local_host_name_cfstr)
 {
     bool succeeded;
@@ -4803,21 +5342,16 @@
     bool succeeded;
     CFStringRef local_host_name_cfstring = NULL;
 
-    if (server_state->stub_router_enabled) {
-        // Set notification from configd.
-        succeeded = monitor_name_changes(server_state->dnssd_proxy_advertisements);
-        require_action_quiet(succeeded, exit, ERROR("failed to monitor name changes"));
+    // Set notification from configd.
+    succeeded = monitor_name_changes(server_state->dnssd_proxy_advertisements);
+    require_action_quiet(succeeded, exit, ERROR("failed to monitor name changes"));
 
-        // Get the initial local host name
-        local_host_name_cfstring = SCDynamicStoreCopyLocalHostName(NULL);
-        require_action_quiet(local_host_name != NULL, exit, succeeded = false; ERROR("failed to get local host name"));
+    // Get the initial local host name
+    local_host_name_cfstring = SCDynamicStoreCopyLocalHostName(NULL);
+    require_action_quiet(local_host_name != NULL, exit, succeeded = false; ERROR("failed to get local host name"));
 
-        succeeded = update_my_name(local_host_name_cfstring);
-        require_action_quiet(succeeded, exit, ERROR("failed to update myname"));
-    } else {
-        succeeded = update_my_name(NULL);
-        require_action_quiet(succeeded, exit, ERROR("failed to update myname"));
-    }
+    succeeded = update_my_name(local_host_name_cfstring);
+    require_action_quiet(succeeded, exit, ERROR("failed to update myname"));
 
 exit:
     if (local_host_name_cfstring != NULL) {
@@ -4826,26 +5360,24 @@
     return succeeded;
 }
 
+#ifndef SRP_TEST_SERVER
 static bool
 configure_dnssd_proxy(void)
 {
-    bool succeeded;
-
-    udp_port= 53;
-    tcp_port = 53;
-    tls_port = 853;
-
-    succeeded = true;
-    return succeeded;
+    dnssd_proxy_udp_port= 53;
+    dnssd_proxy_tcp_port = 53;
+    dnssd_proxy_tls_port = 853;
+    return true;
 }
+#endif // SRP_TEST_SERVER
 #endif // SRP_FEATURE_DYNAMIC_CONFIGURATION
 
 static bool
 start_dnssd_proxy_listener(void)
 {
     bool succeeded;
-    addr_t addr;
 
+#if STUB_ROUTER
 #ifndef NOT_HAVE_SA_LEN
 #  define SA_LEN_INIT addr.sa.sa_len = sizeof(addr.sin6)
 #else
@@ -4859,33 +5391,41 @@
             SA_LEN_INIT;                        \
         } while (false)
 
-    INIT_ADDR_T(udp_port);
-    listener[num_listeners] = ioloop_listener_create(false, false, NULL, 0, &addr, NULL, "DNS UDP Listener",
-                                                     dns_proxy_input, NULL, NULL, NULL, NULL, NULL, NULL);
-    require_action_quiet(listener[num_listeners] != NULL, exit, succeeded = false;
-        ERROR("failed to start UDP listener - listener index: %d", num_listeners));
-    num_listeners++;
+    addr_t addr;
 
-    INIT_ADDR_T(tcp_port);
-    listener[num_listeners] = ioloop_listener_create(true, false, NULL, 0, &addr, NULL, "TCP DNS Listener",
-                                                     dns_proxy_input, NULL, NULL, NULL, NULL, NULL, NULL);
-    require_action_quiet(listener[num_listeners] != NULL, exit, succeeded = false;
-        ERROR("failed to start TCP listener - listener index: %d", num_listeners));
-    num_listeners++;
+    INIT_ADDR_T(dnssd_proxy_udp_port);
+    dnssd_proxy_listeners[dnssd_proxy_num_listeners] =
+        ioloop_listener_create(false, false, true, NULL, 0, &addr, NULL, "DNS over UDP", dns_proxy_input,
+                               NULL, NULL, NULL, NULL, NULL, 0, NULL);
+    require_action_quiet(dnssd_proxy_listeners[dnssd_proxy_num_listeners] != NULL, exit, succeeded = false;
+        ERROR("failed to start UDP listener - listener index: %d", dnssd_proxy_num_listeners));
+    dnssd_proxy_num_listeners++;
 
-    for (int i = 0; i < num_listeners; i++) {
-        INFO("listener started - name: " PUB_S_SRP, listener[i]->name);
-    }
+    INIT_ADDR_T(dnssd_proxy_tcp_port);
+    dnssd_proxy_listeners[dnssd_proxy_num_listeners] =
+        ioloop_listener_create(true, false, true, NULL, 0, &addr, NULL, "DNS over TCP", dns_proxy_input,
+                               NULL, NULL, NULL, NULL, NULL, 0, NULL);
+    require_action_quiet(dnssd_proxy_listeners[dnssd_proxy_num_listeners] != NULL, exit, succeeded = false;
+        ERROR("failed to start TCP listener - listener index: %d", dnssd_proxy_num_listeners));
+    dnssd_proxy_num_listeners++;
+#endif // STUB_ROUTER
 
     dnssd_push_setup();
 
+    for (int i = 0; i < dnssd_proxy_num_listeners; i++) {
+        INFO("listener started - name: " PUB_S_SRP, dnssd_proxy_listeners[i]->name);
+    }
+
     succeeded = true;
+    goto exit;
+
 exit:
     return succeeded;
 }
 
 #define ADVERTISEMENT_RETRY_TIMER 10 * MSEC_PER_SEC
 
+#if STUB_ROUTER
 static void
 advertisements_finalize(void *context)
 {
@@ -5072,13 +5612,174 @@
     return succeeded;
 }
 
+#if SRP_FEATURE_DISCOVERY_PROXY_SERVER
+
 static bool
-advertise_dnssd_proxy(srp_server_t *server_state, const char *const NONNULL domain_to_advertise)
+start_timer_to_advertise_dnssd_dp_proxy(dnssd_dp_proxy_advertisements_t *context, uint32_t interval);
+
+static void
+dp_advertisements_finalize(void *const context)
 {
-    // Start advertisement (wait for ADVERTISEMENT_RETRY_TIMER to allow mDNSResponder to start).
-    return start_timer_to_advertise(server_state->dnssd_proxy_advertisements, domain_to_advertise, ADVERTISEMENT_RETRY_TIMER);
+    dnssd_dp_proxy_advertisements_t *advertisements_context = context;
+    advertisements_context->txn = NULL;
 }
 
+static void
+dp_advertisements_failed(void *const UNUSED context, const int status)
+{
+    ERROR("push service advertisement failed  -- error: %d", status);
+}
+
+static void
+dp_advertisements_callback(const DNSServiceRef UNUSED sd_ref, const DNSServiceFlags UNUSED flags,
+                           const DNSServiceErrorType error, const char *const name, const char *const reg_type,
+                           const char *const domain, void *const context)
+{
+    dnssd_dp_proxy_advertisements_t *const ads_ctx = context;
+    if (error == kDNSServiceErr_NoError) {
+        INFO("Push service registered successfully -- %s.%s%s", name, reg_type, domain);
+    } else if (error == kDNSServiceErr_ServiceNotRunning) {
+        if (ads_ctx->txn != NULL) {
+            ioloop_dnssd_txn_cancel(ads_ctx->txn);
+            ioloop_dnssd_txn_forget(&ads_ctx->txn);
+        }
+        ads_ctx->service_ref = NULL;
+
+        const bool succeeded = start_timer_to_advertise_dnssd_dp_proxy(ads_ctx, ADVERTISEMENT_RETRY_TIMER);
+        if (!succeeded) {
+            ERROR("start_timer_to_advertise_dnssd_dp_proxy failed");
+        } else {
+            INFO("mDNSResponder stopped running, preparing to re-advertise DNS push service");
+        }
+    } else {
+        ERROR("Push service not registered -- error: %d", error);
+    }
+}
+
+static void
+advertise_dnssd_dp_proxy_callback(void *const context)
+{
+    DNSServiceRef service_ref = NULL;
+    dnssd_txn_t *txn = NULL;
+    dnssd_dp_proxy_advertisements_t *const advertisement_context = context;
+    srp_server_t *const server_state = advertisement_context->server_state;
+    int bytes_written = 0;
+
+    // Construct ,_local,_openthread#thread#home#arpa
+    const char *domains_support_push[] = {
+        DOT_LOCAL_DOMAIN,
+        THREAD_BROWSING_DOMAIN,
+    };
+    char subtype_domains[256];
+    size_t current_len = 0;
+    for (size_t i = 0; i < countof(domains_support_push); i++) {
+        const char *const domain_supports_push = domains_support_push[i];
+        const size_t len = strlen(domain_supports_push);
+        char subtype_domain[128];
+        // Convert domain name like `_openthread.thread.home.arpa` to `_openthread#thread#home#arpa`.
+        require_quiet(len < sizeof(subtype_domain), exit);
+        for (size_t j = 0; j < len + 1; j++) {
+            if (domain_supports_push[j] == '.') {
+                subtype_domain[j] = '#';
+            } else {
+                subtype_domain[j] = domain_supports_push[j];
+            }
+        }
+        // Remove the trailing '#'.
+        if (subtype_domain[len - 1] == '#') {
+            subtype_domain[len - 1] = '\0';
+        }
+
+        bytes_written = snprintf(subtype_domains + current_len, sizeof(subtype_domains) - current_len, ",_%s",
+                                 subtype_domain);
+        require_quiet((bytes_written > 0) && ((size_t)bytes_written < (sizeof(subtype_domains) - current_len)), exit);
+        current_len += bytes_written;
+    }
+
+    // Construct _dnssd-dp._tcp,_local,_openthread#thread#home#arpa with subtype.
+    char reg_type[128];
+    bytes_written = snprintf(reg_type, sizeof(reg_type), "_dnssd-dp._tcp%s", subtype_domains);
+    require_quiet((bytes_written > 0) && ((size_t)bytes_written < sizeof(reg_type)), exit);
+
+    // Construct service instance name like: p128-undulw2d1vktd1
+    const uint8_t priority = 128;
+    const char * const random_identifier = uuid_name;
+    char name[128];
+    bytes_written = snprintf(name, sizeof(name), "p%03d-%s", priority, random_identifier);
+    require_quiet((bytes_written > 0) && ((size_t)bytes_written < sizeof(name)), exit);
+
+    const uint16_t dp_port = dnssd_proxy_tls_port;
+
+    // The registered PTRs will be like:
+    // _openthread#thread#home#arpa._sub._dnssd-dp._tcp.local   PTR p128-undulw2d1vktd1._dnssd-dp._tcp.local
+    // _local._sub._dnssd-dp._tcp.local                         PTR p128-undulw2d1vktd1._dnssd-dp._tcp.local
+    const DNSServiceErrorType dnsssd_err = DNSServiceRegister(&service_ref, 0, server_state->advertise_interface, name,
+        reg_type, DOT_LOCAL_DOMAIN, NULL, htons(dp_port), 0, NULL, dp_advertisements_callback, context);
+    require_action_quiet(dnsssd_err == kDNSServiceErr_NoError, exit,
+        ERROR("DNSServiceRegisterRecord failed -- error: %d", dnsssd_err));
+
+    txn = ioloop_dnssd_txn_add(service_ref, advertisement_context, dp_advertisements_finalize,
+                               dp_advertisements_failed);
+    require_quiet(txn != NULL, exit);
+
+    advertisement_context->service_ref = service_ref;
+    service_ref = NULL;
+    advertisement_context->txn = txn;
+    txn = NULL;
+    INFO("Advertising push discovery service -- reg_type: %s.%s, service instance name: %s._dnssd-dp._tcp.%s",
+         reg_type, DOT_LOCAL_DOMAIN, name, DOT_LOCAL_DOMAIN);
+
+exit:
+    DNSServiceRefSourceForget(&service_ref);
+    if (txn != NULL) {
+        ioloop_dnssd_txn_cancel(txn);
+        ioloop_dnssd_txn_forget(&txn);
+    }
+}
+
+static bool
+start_timer_to_advertise_dnssd_dp_proxy(dnssd_dp_proxy_advertisements_t *const context, const uint32_t interval)
+{
+    bool succeeded;
+    wakeup_t *wakeup_timer = context->wakeup_timer;
+
+    if (wakeup_timer == NULL) {
+        wakeup_timer = ioloop_wakeup_create();
+    } else {
+        ioloop_wakeup_retain(wakeup_timer);
+    }
+    require_action_quiet(wakeup_timer != NULL, exit, succeeded = false);
+
+    succeeded = ioloop_add_wake_event(wakeup_timer, context, advertise_dnssd_dp_proxy_callback, NULL, interval);
+    require_quiet(succeeded, exit);
+
+    if (context->wakeup_timer == NULL) {
+        context->wakeup_timer = wakeup_timer;
+        wakeup_timer = NULL;
+    }
+
+exit:
+    ioloop_wakeup_forget(&wakeup_timer);
+    return succeeded;
+}
+
+static bool
+advertise_dnssd_dp_proxy(srp_server_t *const server_state)
+{
+    return start_timer_to_advertise_dnssd_dp_proxy(server_state->dnssd_dp_proxy_advertisements,
+        ADVERTISEMENT_RETRY_TIMER);
+}
+
+static bool
+is_eligible_to_provide_push_discovery_service(void)
+{
+    return true;
+}
+
+#endif // SRP_FEATURE_DISCOVERY_PROXY_SERVER
+
+#endif // STUB_ROUTER
+
 #if SRP_FEATURE_COMBINED_SRP_DNSSD_PROXY
 #  if SRP_FEATURE_DYNAMIC_CONFIGURATION
 static bool
@@ -5090,12 +5791,16 @@
     served_domain_t *ipv4 = NULL;
     served_domain_t *thread_served_domain = NULL;
     served_domain_t *default_service_arpa_domain = NULL;
+#if SRP_FEATURE_LOCAL_DISCOVERY
+    served_domain_t *dot_local_domain = NULL;
+#endif
 
     // <local host name>.home.arpa.
     my_name_served_domain = new_served_domain(NULL, my_name);
     require_action_quiet(my_name_served_domain != NULL, exit, succeeded = false;
         ERROR("failed to create new served domain - domain name: " PUB_S_SRP, my_name));
 
+#if STUB_ROUTER
     if (server_state->stub_router_enabled) {
         // ip6.arpa.
         // in-addr.arpa.
@@ -5106,6 +5811,9 @@
                                    IPV6_REVERSE_LOOKUP_DOMAIN, IPV4_REVERSE_LOOKUP_DOMAIN)
             );
     }
+#else
+    (void)server_state;
+#endif
 
     // THREAD_BROWSING_DOMAIN
     // It will be served by kDNSServiceInterfaceIndexLocalOnly, which is a pseudo interface.
@@ -5121,6 +5829,15 @@
     hardwired_set = dnssd_hardwired_setup_for_served_domain(default_service_arpa_domain);
     require_action_quiet(hardwired_set, exit, succeeded = false);
 
+#if SRP_FEATURE_LOCAL_DISCOVERY
+    // local
+    // discovery proxy for the infrastructure interface
+    dot_local_domain = add_new_served_domain_with_interface(INFRASTRUCTURE_PSEUDO_INTERFACE, NULL, NULL);
+    require_action_quiet(dot_local_domain != NULL, exit, succeeded = false);
+    hardwired_set = dnssd_hardwired_setup_for_served_domain(dot_local_domain);
+    require_action_quiet(hardwired_set, exit, succeeded = false);
+#endif
+
     succeeded = true;
 exit:
     if (!succeeded) {
@@ -5130,6 +5847,11 @@
         if (default_service_arpa_domain != NULL) {
             delete_served_domain(default_service_arpa_domain);
         }
+#if SRP_FEATURE_LOCAL_DISCOVERY
+        if (dot_local_domain != NULL) {
+            delete_served_domain(dot_local_domain);
+        }
+#endif
         if (ipv4 != NULL) {
             delete_served_domain(ipv4);
         }
@@ -5149,7 +5871,6 @@
 init_dnssd_proxy(srp_server_t *server_state)
 {
     bool succeeded;
-
     dnssd_proxy_advertisements_t *advertisements = server_state->dnssd_proxy_advertisements;
     if (advertisements == NULL) {
         advertisements = calloc(1, sizeof(*advertisements));
@@ -5161,12 +5882,37 @@
         advertisements->sc_context.info = advertisements;
     }
 
+#if STUB_ROUTER
+#if SRP_FEATURE_DISCOVERY_PROXY_SERVER
+    if (is_eligible_to_provide_push_discovery_service()) {
+        dnssd_dp_proxy_advertisements_t *dp_ads = server_state->dnssd_dp_proxy_advertisements;
+        if (dp_ads == NULL) {
+            dp_ads = calloc(1, sizeof(*dp_ads));
+            require_action_quiet(dp_ads != NULL, exit,
+                                 succeeded = false; ERROR("no memory for push discovery service advertisements"));
+            server_state->dnssd_dp_proxy_advertisements = dp_ads;
+            dp_ads->server_state = server_state;
+        }
+    }
+#endif // SRP_FEATURE_DISCOVERY_PROXY_SERVER
+#endif // STUB_ROUTER
+
 #if SRP_FEATURE_DYNAMIC_CONFIGURATION
     succeeded = configure_dnssd_proxy();
     require_action_quiet(succeeded, exit, ERROR("configure_dnssd_proxy failed"));
 
+
     succeeded = initialize_my_name_and_monitoring(server_state);
+
     require_action_quiet(succeeded, exit, ERROR("initialize_my_name_and_monitoring failed"));
+    succeeded = initialize_uuid_name(server_state);
+    require_action_quiet(succeeded, exit, ERROR("initialize_uuid_name failed"));
+#if STUB_ROUTER
+    if (!server_state->stub_router_enabled) {
+        served_domain_process_name_change();
+    }
+#endif
+
 #else // SRP_FEATURE_DYNAMIC_CONFIGURATION
     // Read the config file
     succeeded = config_parse(NULL, "/etc/dnssd-proxy.cf", dp_verbs, NUMCFVERBS);
@@ -5179,7 +5925,7 @@
                          exit,
                          ERROR("Please configure at least one my-ipv4-addr and/or one my-ipv6-addr."));
 
-    ioloop_map_interface_addresses(NULL, &served_domains, dnssd_proxy_ifaddr_callback);
+    ioloop_map_interface_addresses(server_state, NULL, &served_domains, dnssd_proxy_ifaddr_callback);
 
     // Set up hardwired answers
     dnssd_hardwired_setup();
@@ -5210,8 +5956,16 @@
     succeeded = start_dnssd_proxy_listener();
     require_action_quiet(succeeded, exit, ERROR("start_dnssd_proxy_listener failed"));
 
-    succeeded = advertise_dnssd_proxy(server_state, THREAD_BROWSING_DOMAIN);
-    require_action_quiet(succeeded, exit, ERROR("advertise_dnssd_proxy failed"));
+#if STUB_ROUTER
+    if (server_state->stub_router_enabled) {
+    #if SRP_FEATURE_DISCOVERY_PROXY_SERVER
+        if (server_state->dnssd_dp_proxy_advertisements != NULL) {
+            succeeded = advertise_dnssd_dp_proxy(server_state);
+            require_action_quiet(succeeded, exit, ERROR("advertise_dnssd_dp_proxy failed"));
+        }
+    #endif
+    }
+#endif
 
 #if SRP_FEATURE_DYNAMIC_CONFIGURATION
     succeeded = served_domain_init(server_state);
@@ -5228,8 +5982,8 @@
     int i;
     bool log_stderr = false;
 
-    udp_port = tcp_port = 53;
-    tls_port = 853;
+    dnssd_proxy_udp_port = dnssd_proxy_tcp_port = 53;
+    dnssd_proxy_tls_port = 853;
 
     // Parse command line arguments
     for (i = 1; i < argc; i++) {
diff --git a/ServiceRegistration/dnssd-proxy.h b/ServiceRegistration/dnssd-proxy.h
index 527802a..ef2939a 100644
--- a/ServiceRegistration/dnssd-proxy.h
+++ b/ServiceRegistration/dnssd-proxy.h
@@ -1,6 +1,6 @@
 /* dnssd-proxy.h
  *
- * Copyright (c) 2018-2021 Apple Inc. All rights reserved.
+ * Copyright (c) 2018-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -28,6 +28,15 @@
 // MARK: - Macros
 
 #define MAX_ADDRS 10
+extern comm_t *NULLABLE dnssd_proxy_listeners[4 + MAX_ADDRS];
+extern int dnssd_proxy_num_listeners;
+extern uint16_t dnssd_proxy_udp_port;
+extern uint16_t dnssd_proxy_tcp_port;
+extern uint16_t dnssd_proxy_tls_port;
+
+#ifdef SRP_TEST_SERVER
+bool configure_dnssd_proxy(void);
+#endif
 
 #if (!SRP_FEATURE_COMBINED_SRP_DNSSD_PROXY)
     // FIXME: set CERTWRITE_PROGRAM and GENKEY_PROGRAM to correct one.
@@ -43,16 +52,39 @@
 #define THREAD_NETWORK_NAME "openthread"
 #define THREAD_BROWSING_DOMAIN THREAD_NETWORK_NAME ".thread.home.arpa."
 
+#define LOCAL_ONLY_PSEUDO_INTERFACE "local only pseudo interface"
+#define ALL_LOCALS_PSEUDO_INTERFACE "all locally-discoverable services pseudo interface"
+#define INFRASTRUCTURE_PSEUDO_INTERFACE "infrastructure interface"
+
+#define DNS_OVER_TLS_DEFAULT_PORT 853
+
+typedef struct served_domain served_domain_t;
+
 //======================================================================================================================
 // MARK: - Functions
 
 // We can only initialize dnssd-proxy in srp-mdns-proxy if we combined it with srp-mdns-proxy.
 #if (SRP_FEATURE_COMBINED_SRP_DNSSD_PROXY) && !defined(RA_TESTER)
+#ifdef SRP_TEST_SERVER
+extern served_domain_t *NULLABLE last_freed_domain;
+#endif
 bool init_dnssd_proxy(srp_server_t *NONNULL server_state);
-bool delete_served_domain_by_interface_name(const char *const NONNULL interface_name);
+served_domain_t *NULLABLE delete_served_domain_by_interface_name(const char *const NONNULL interface_name);
 #endif // #if (SRP_FEATURE_COMBINED_SRP_DNSSD_PROXY)
 
-void dnssd_proxy_ifaddr_callback(void *NULLABLE context, const char *NONNULL name, const addr_t *NONNULL address,
-								 const addr_t *NONNULL mask, uint32_t UNUSED flags, enum interface_address_change event_type);
+void dnssd_proxy_ifaddr_callback(srp_server_t *NULLABLE server_state, void *NULLABLE context, const char *NONNULL name,
+                                 const addr_t *NONNULL address, const addr_t *NONNULL mask, uint32_t UNUSED flags,
+                                 enum interface_address_change event_type);
 void dp_start_dropping(void);
+void dns_proxy_input_for_server(comm_t *NONNULL comm,
+                                srp_server_t *NONNULL server_state, message_t *NONNULL message, void *NULLABLE context);
 #endif // #ifndef __DNSSD_PROXY_H__
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/dso-utils.c b/ServiceRegistration/dso-utils.c
index a1b0842..df8b42b 100644
--- a/ServiceRegistration/dso-utils.c
+++ b/ServiceRegistration/dso-utils.c
@@ -35,8 +35,35 @@
     dns_qr_set(&response, dns_qr_response);
     dns_opcode_set(&response, dns_opcode_get(wire));
     dns_rcode_set(&response, rcode);
+
+    size_t length = DNS_HEADER_SIZE;
+    uint16_t wire_length = message != NULL ? message->length : (uint16_t)sizeof(*wire);
+    dns_rr_t question;
+    memset(&question, 0, sizeof(question));
+    unsigned offp = 0;
+    if (ntohs(wire->qdcount) == 1 &&
+        dns_rr_parse(&question, wire->data, wire_length - DNS_HEADER_SIZE, &offp, false, false))
+    {
+        dns_towire_state_t towire;
+        memset(&towire, 0, sizeof(towire));
+        towire.p = &response.data[0];
+        towire.lim = ((uint8_t *)&response) + sizeof(response);
+        towire.message = &response;
+
+        size_t namelen = dns_name_to_wire_canonical(towire.p, towire.lim - towire.p, question.name);
+        if (namelen != 0) {
+            towire.p += namelen;
+            dns_u16_to_wire(&towire, question.type);
+            dns_u16_to_wire(&towire, question.qclass);
+            if (!towire.truncated && !towire.error) {
+                response.qdcount = htons(1);
+                length += towire.p - (uint8_t *)&response.data[0];
+            }
+        }
+        dns_name_free(question.name);
+    }
     iov.iov_base = &response;
-    iov.iov_len = DNS_HEADER_SIZE; // No RRs
+    iov.iov_len = length; // No RRs
     ioloop_send_message(comm, message, &iov, 1);
 }
 
diff --git a/ServiceRegistration/fromwire.c b/ServiceRegistration/fromwire.c
index 1437052..731a310 100644
--- a/ServiceRegistration/fromwire.c
+++ b/ServiceRegistration/fromwire.c
@@ -259,11 +259,27 @@
 static void
 dns_rrdata_dump(dns_rr_t *rr, bool dump_to_stderr)
 {
+    char outbuf[2048];
+
+    dns_rdata_dump_to_buf(rr, outbuf, sizeof(outbuf));
+
+    if (dump_to_stderr) {
+        fprintf(stderr, "%s\n", outbuf);
+    } else {
+        DEBUG(PUB_S_SRP, outbuf);
+    }
+}
+
+size_t
+dns_rdata_dump_to_buf(dns_rr_t *rr, char *outbuf, size_t bufsize)
+{
     char nbuf[INET6_ADDRSTRLEN];
     char buf[DNS_MAX_NAME_SIZE_ESCAPED + 1];
-    char outbuf[2048];
+    size_t output_len, avail = bufsize;
     char *obp;
-    size_t output_len, avail = 2048;
+
+    obp = outbuf;
+    avail = bufsize;
 
 #define ADVANCE(result, start, remaining) \
     output_len = strlen(start);           \
@@ -278,16 +294,15 @@
         }                    \
     } while (0)
 
-
     switch(rr->type) {
     case dns_rrtype_key:
-        snprintf(outbuf, sizeof(outbuf),
+        snprintf(outbuf, bufsize,
                  "KEY <AC %d> <Z %d> <XT %d> <ZZ %d> <NAMTYPE %d> <ZZZZ %d> <ORY %d> %d %d ",
                  ((rr->data.key.flags & 0xC000) >> 14 & 3), ((rr->data.key.flags & 0x2000) >> 13) & 1,
                  ((rr->data.key.flags & 0x1000) >> 12) & 1, ((rr->data.key.flags & 0xC00) >> 10) & 3,
                  ((rr->data.key.flags & 0x300) >> 8) & 3, ((rr->data.key.flags & 0xF0) >> 4) & 15,
                  rr->data.key.flags & 15, rr->data.key.protocol, rr->data.key.algorithm);
-        ADVANCE(obp, outbuf, sizeof outbuf);
+        ADVANCE(obp, outbuf, bufsize);
 
         for (unsigned i = 0; i < rr->data.key.len; i++) {
             if (i == 0) {
@@ -303,11 +318,11 @@
 
     case dns_rrtype_sig:
         dns_name_print(rr->data.sig.signer, buf, sizeof(buf));
-        snprintf(outbuf, sizeof(outbuf), "SIG %d %d %d %lu %lu %lu %d %s",
+        snprintf(outbuf, bufsize, "SIG %d %d %d %lu %lu %lu %d %s",
                  rr->data.sig.type, rr->data.sig.algorithm, rr->data.sig.label,
                  (unsigned long)rr->data.sig.rrttl, (unsigned long)rr->data.sig.expiry,
                  (unsigned long)rr->data.sig.inception, rr->data.sig.key_tag, buf);
-        ADVANCE(obp, outbuf, sizeof outbuf);
+        ADVANCE(obp, outbuf, bufsize);
         for (unsigned i = 0; i < rr->data.sig.len; i++) {
             if (i == 0) {
                 snprintf(obp, avail, "%d [%02x", rr->data.sig.len, rr->data.sig.signature[i]);
@@ -322,38 +337,48 @@
 
     case dns_rrtype_srv:
         dns_name_print(rr->data.srv.name, buf, sizeof(buf));
-        snprintf(outbuf, sizeof(outbuf), "SRV %d %d %d %s", rr->data.srv.priority, rr->data.srv.weight,
+        snprintf(outbuf, bufsize, "SRV %d %d %d %s", rr->data.srv.priority, rr->data.srv.weight,
                  rr->data.srv.port, buf);
-        ADVANCE(obp, outbuf, sizeof(outbuf));
+        ADVANCE(obp, outbuf, bufsize);
         break;
 
     case dns_rrtype_ptr:
         dns_name_print(rr->data.ptr.name, buf, sizeof(buf));
-        snprintf(outbuf, sizeof(outbuf), "PTR %s", buf);
-        ADVANCE(obp, outbuf, sizeof(outbuf));
+        snprintf(outbuf, bufsize, "PTR %s", buf);
+        ADVANCE(obp, outbuf, bufsize);
         break;
 
     case dns_rrtype_cname:
         dns_name_print(rr->data.cname.name, buf, sizeof(buf));
-        snprintf(outbuf, sizeof(outbuf), "CNAME %s", buf);
-        ADVANCE(obp, outbuf, sizeof(outbuf));
+        snprintf(outbuf, bufsize, "CNAME %s", buf);
+        ADVANCE(obp, outbuf, bufsize);
+        break;
+
+    case dns_rrtype_soa:
+        dns_name_print(rr->data.soa.mname, buf, sizeof(buf));
+        snprintf(outbuf, bufsize, "SOA %s", buf);
+        ADVANCE(obp, outbuf, bufsize);
+        dns_name_print(rr->data.soa.rname, buf, sizeof(buf));
+        snprintf(outbuf, bufsize, "%s %u %d %d %d %d", buf, rr->data.soa.serial, rr->data.soa.refresh,
+                 rr->data.soa.retry, rr->data.soa.expire, rr->data.soa.minimum);
+        ADVANCE(obp, outbuf, bufsize);
         break;
 
     case dns_rrtype_a:
         inet_ntop(AF_INET, &rr->data.a, nbuf, sizeof(nbuf));
-        snprintf(outbuf, sizeof(outbuf), "A %s", nbuf);
-        ADVANCE(obp, outbuf, sizeof(outbuf));
+        snprintf(outbuf, bufsize, "A %s", nbuf);
+        ADVANCE(obp, outbuf, bufsize);
         break;
 
     case dns_rrtype_aaaa:
         inet_ntop(AF_INET6, &rr->data.aaaa, nbuf, sizeof(nbuf));
-        snprintf(outbuf, sizeof(outbuf), "AAAA %s", nbuf);
-        ADVANCE(obp, outbuf, sizeof(outbuf));
+        snprintf(outbuf, bufsize, "AAAA %s", nbuf);
+        ADVANCE(obp, outbuf, bufsize);
         break;
 
     case dns_rrtype_txt:
         strcpy(outbuf, "TXT ");
-        ADVANCE(obp, outbuf, sizeof(outbuf));
+        ADVANCE(obp, outbuf, bufsize);
         for (unsigned i = 0; i < rr->data.txt.len; i++) {
             if (isascii(rr->data.txt.data[i]) && isprint(rr->data.txt.data[i])) {
                 DEPCHAR(rr->data.txt.data[i]);
@@ -366,8 +391,8 @@
         break;
 
     default:
-        snprintf(outbuf, sizeof(outbuf), "<rrtype %d>:", rr->type);
-        ADVANCE(obp, outbuf, sizeof(outbuf));
+        snprintf(outbuf, bufsize, "<rrtype %d>:", rr->type);
+        ADVANCE(obp, outbuf, bufsize);
         if (rr->data.unparsed.len == 0) {
             snprintf(obp, avail, " <none>");
             ADVANCE(obp, obp, avail);
@@ -379,11 +404,8 @@
         }
         break;
     }
-    if (dump_to_stderr) {
-        fprintf(stderr, "%s\n", outbuf);
-    } else {
-        DEBUG(PUB_S_SRP, outbuf);
-    }
+    *obp = 0;
+    return obp - buf;
 }
 
 bool
@@ -444,7 +466,8 @@
     case dns_rrtype_srv:
         if (!dns_u16_parse(buf, target, offp, &rr->data.srv.priority) ||
             !dns_u16_parse(buf, target, offp, &rr->data.srv.weight) ||
-            !dns_u16_parse(buf, target, offp, &rr->data.srv.port)) {
+            !dns_u16_parse(buf, target, offp, &rr->data.srv.port))
+        {
             return false;
         }
         // This fallthrough assumes that the first element in the srv, ptr and cname structs is
@@ -458,6 +481,23 @@
         }
         break;
 
+    case dns_rrtype_soa:
+        if (!dns_name_parse_(&rr->data.soa.mname, buf, target, offp, *offp, file, line)) {
+            return false;
+        }
+        if (!dns_name_parse_(&rr->data.soa.rname, buf, target, offp, *offp, file, line)) {
+            return false;
+        }
+        if (!dns_u32_parse(buf, target, offp, &rr->data.soa.serial) ||
+            !dns_u32_parse(buf, target, offp, &rr->data.soa.refresh) ||
+            !dns_u32_parse(buf, target, offp, &rr->data.soa.retry) ||
+            !dns_u32_parse(buf, target, offp, &rr->data.soa.expire) ||
+            !dns_u32_parse(buf, target, offp, &rr->data.soa.minimum))
+        {
+            return false;
+        }
+        break;
+
     case dns_rrtype_a:
         if (rdlen != 4) {
             DEBUG("dns_rdata_parse: A rdlen is not 4: %u", rdlen);
@@ -612,6 +652,15 @@
 #endif
             break;
 
+    case dns_rrtype_soa:
+        if (rr->data.soa.mname != NULL) {
+            dns_name_free(rr->data.soa.mname);
+        }
+        if (rr->data.soa.rname != NULL) {
+            dns_name_free(rr->data.soa.rname);
+        }
+        break;
+
     case dns_rrtype_txt:
         free(rr->data.txt.data);
 #ifndef __clang_analyzer__
diff --git a/ServiceRegistration/icmp.c b/ServiceRegistration/icmp.c
new file mode 100644
index 0000000..176029d
--- /dev/null
+++ b/ServiceRegistration/icmp.c
@@ -0,0 +1,963 @@
+/* icmp.c
+ *
+ * Copyright (c) 2019-2023 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This code implements ICMP I/O functions for the Thread border router.
+ */
+
+#ifndef LINUX
+#include <netinet/in.h>
+#include <net/if.h>
+#include <netinet6/in6_var.h>
+#include <netinet6/nd6.h>
+#include <net/if_media.h>
+#include <sys/stat.h>
+#else
+#define _GNU_SOURCE
+#include <netinet/in.h>
+#include <fcntl.h>
+#include <bsd/stdlib.h>
+#include <net/if.h>
+#endif
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <net/route.h>
+#include <netinet/icmp6.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <arpa/inet.h>
+#if !USE_SYSCTL_COMMAND_TO_ENABLE_FORWARDING
+#ifndef LINUX
+#include <sys/sysctl.h>
+#endif // LINUX
+#endif // !USE_SYSCTL_COMMAND_TO_ENABLE_FORWARDING
+#include <stdlib.h>
+#include <stddef.h>
+#include <dns_sd.h>
+#include <inttypes.h>
+#include <signal.h>
+
+#ifdef IOLOOP_MACOS
+#include <xpc/xpc.h>
+
+#include <TargetConditionals.h>
+#include <SystemConfiguration/SystemConfiguration.h>
+#include <SystemConfiguration/SCPrivate.h>
+#include <SystemConfiguration/SCNetworkConfigurationPrivate.h>
+#include <SystemConfiguration/SCNetworkSignature.h>
+#include <network_information.h>
+
+#include <CoreUtils/CoreUtils.h>
+#endif // IOLOOP_MACOS
+
+#include "srp.h"
+#include "dns-msg.h"
+#include "ioloop.h"
+#include "cti-services.h"
+#include "srp-mdns-proxy.h"
+#include "route.h"
+#include "icmp.h"
+#include "state-machine.h"
+#include "thread-service.h"
+#include "omr-watcher.h"
+
+
+icmp_listener_t icmp_listener;
+
+void
+icmp_message_free(icmp_message_t *message)
+{
+    if (message->options != NULL) {
+        free(message->options);
+    }
+    if (message->wakeup != NULL) {
+        ioloop_cancel_wake_event(message->wakeup);
+        ioloop_wakeup_release(message->wakeup);
+    }
+    free(message);
+}
+
+void
+icmp_message_dump(icmp_message_t *message,
+                  struct in6_addr *source_address, struct in6_addr *destination_address)
+{
+    link_layer_address_t *lladdr;
+    prefix_information_t *prefix_info;
+    route_information_t *route_info;
+    uint8_t *flags;
+    int i;
+    char retransmission_timer_buf[11]; // Maximum size of a uint32_t printed as decimal.
+    char *retransmission_timer = "infinite";
+
+    if (message->retransmission_timer != ND6_INFINITE_LIFETIME) {
+        snprintf(retransmission_timer_buf, sizeof(retransmission_timer_buf), "%" PRIu32, message->retransmission_timer);
+        retransmission_timer = retransmission_timer_buf;
+    }
+
+    SEGMENTED_IPv6_ADDR_GEN_SRP(source_address->s6_addr, src_addr_buf);
+    SEGMENTED_IPv6_ADDR_GEN_SRP(destination_address->s6_addr, dst_addr_buf);
+    if (message->type == icmp_type_router_advertisement) {
+        INFO("router advertisement from " PRI_SEGMENTED_IPv6_ADDR_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP
+             " hop_limit %d on " PUB_S_SRP ": checksum = %x "
+             "cur_hop_limit = %d flags = %x router_lifetime = %d reachable_time = %" PRIu32
+             " retransmission_timer = " PUB_S_SRP,
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(source_address->s6_addr, src_addr_buf),
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(destination_address->s6_addr, dst_addr_buf),
+             message->hop_limit, message->interface->name, message->checksum, message->cur_hop_limit, message->flags,
+             message->router_lifetime, message->reachable_time, retransmission_timer);
+    } else if (message->type == icmp_type_router_solicitation) {
+        INFO("router solicitation from " PRI_SEGMENTED_IPv6_ADDR_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP
+             " hop_limit %d on " PUB_S_SRP ": code = %d checksum = %x",
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(source_address->s6_addr, src_addr_buf),
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(destination_address->s6_addr, dst_addr_buf),
+             message->hop_limit, message->interface->name,
+             message->code, message->checksum);
+    } else {
+        INFO("icmp message from " PRI_SEGMENTED_IPv6_ADDR_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP " hop_limit %d on "
+             PUB_S_SRP ": type = %d code = %d checksum = %x",
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(source_address->s6_addr, src_addr_buf),
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(destination_address->s6_addr, dst_addr_buf),
+             message->hop_limit, message->interface->name, message->type,
+             message->code, message->checksum);
+    }
+
+    for (i = 0; i < message->num_options; i++) {
+        icmp_option_t *option = &message->options[i];
+        switch(option->type) {
+        case icmp_option_source_link_layer_address:
+            lladdr = &option->option.link_layer_address;
+            INFO("  source link layer address " PRI_MAC_ADDR_SRP, MAC_ADDR_PARAM_SRP(lladdr->address));
+            break;
+        case icmp_option_target_link_layer_address:
+            lladdr = &option->option.link_layer_address;
+            INFO("  destination link layer address " PRI_MAC_ADDR_SRP, MAC_ADDR_PARAM_SRP(lladdr->address));
+            break;
+        case icmp_option_prefix_information:
+            prefix_info = &option->option.prefix_information;
+            SEGMENTED_IPv6_ADDR_GEN_SRP(prefix_info->prefix.s6_addr, prefix_buf);
+            INFO("  prefix info: " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d %x %" PRIu32 " %" PRIu32,
+                 SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix_info->prefix.s6_addr, prefix_buf), prefix_info->length,
+                 prefix_info->flags, prefix_info->valid_lifetime, prefix_info->preferred_lifetime);
+            break;
+        case icmp_option_route_information:
+            route_info = &option->option.route_information;
+                SEGMENTED_IPv6_ADDR_GEN_SRP(route_info->prefix.s6_addr, router_prefix_buf);
+            INFO("  route info: " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d %x %d",
+                 SEGMENTED_IPv6_ADDR_PARAM_SRP(route_info->prefix.s6_addr, router_prefix_buf), route_info->length,
+                 route_info->flags, route_info->route_lifetime);
+            break;
+        case icmp_option_ra_flags_extension:
+            flags = option->option.ra_flags_extension;
+            INFO("  ra flags extension: %x %x %x %x %x %x", flags[0], flags[1], flags[2], flags[3], flags[4], flags[5]);
+            break;
+        default:
+            INFO("  option type %d", option->type);
+            break;
+        }
+    }
+}
+
+static bool
+icmp_message_parse_options(icmp_message_t *message, uint8_t *icmp_buf, unsigned length, unsigned *offset)
+{
+    uint8_t option_type, option_length_8;
+    unsigned option_length;
+    unsigned scan_offset = *offset;
+    icmp_option_t *option;
+    uint32_t reserved32;
+    prefix_information_t *prefix_information;
+    route_information_t *route_information;
+
+    int prefix_bytes;
+
+    // Count the options and validate the lengths
+    message->num_options = 0;
+    while (scan_offset < length) {
+        if (!dns_u8_parse(icmp_buf, length, &scan_offset, &option_type)) {
+            return false;
+        }
+        if (!dns_u8_parse(icmp_buf, length, &scan_offset, &option_length_8)) {
+            return false;
+        }
+        if (option_length_8 == 0) { // RFC4191 section 4.6: The value 0 is invalid.
+            ERROR("icmp_option_parse: option type %d length 0 is invalid.", option_type);
+            return false;
+        }
+        if (scan_offset + option_length_8 * 8 - 2 > length) {
+            ERROR("icmp_option_parse: option type %d length %d is longer than remaining available space %u",
+                  option_type, option_length_8 * 8, length - scan_offset + 2);
+            return false;
+        }
+        scan_offset += option_length_8 * 8 - 2;
+        message->num_options++;
+    }
+    // If there are no options, we're done. No options is valid, so return true.
+    if (message->num_options == 0) {
+        return true;
+    }
+    message->options = calloc(message->num_options, sizeof(*message->options));
+    if (message->options == NULL) {
+        ERROR("No memory for icmp options.");
+        return false;
+    }
+    option = message->options;
+    while (*offset < length) {
+        scan_offset = *offset;
+        if (!dns_u8_parse(icmp_buf, length, &scan_offset, &option_type)) {
+            return false;
+        }
+        if (!dns_u8_parse(icmp_buf, length, &scan_offset, &option_length_8)) {
+            return false;
+        }
+        // We already validated the length in the previous pass.
+        option->type = option_type;
+        option_length = option_length_8 * 8;
+
+        switch(option_type) {
+        case icmp_option_source_link_layer_address:
+        case icmp_option_target_link_layer_address:
+            // At this juncture we are assuming that everything we care about looks like an
+            // ethernet interface.  So for this case, length should be 8.
+            if (option_length != 8) {
+                INFO("Ignoring unexpectedly long link layer address: %d", option_length);
+                // Don't store the option.
+                message->num_options--;
+                *offset += option_length;
+                continue;
+            }
+            option->option.link_layer_address.length = 6;
+            memcpy(option->option.link_layer_address.address, &icmp_buf[scan_offset], 6);
+            break;
+        case icmp_option_prefix_information:
+            prefix_information = &option->option.prefix_information;
+            // Only a length of 32 is valid.  This is an invalid ICMP packet, not just misunderunderstood
+            if (option_length != 32) {
+                return false;
+            }
+            // prefix length 8
+            if (!dns_u8_parse(icmp_buf, length, &scan_offset, &prefix_information->length)) {
+                return false;
+            }
+            // flags 8a
+            if (!dns_u8_parse(icmp_buf, length, &scan_offset, &prefix_information->flags)) {
+                return false;
+            }
+            // valid lifetime 32
+            if (!dns_u32_parse(icmp_buf, length, &scan_offset,
+                               &prefix_information->valid_lifetime)) {
+                return false;
+            }
+            // preferred lifetime 32
+            if (!dns_u32_parse(icmp_buf, length, &scan_offset,
+                               &prefix_information->preferred_lifetime)) {
+                return false;
+            }
+            // reserved2 32
+            if (!dns_u32_parse(icmp_buf, length, &scan_offset, &reserved32)) {
+                return false;
+            }
+            // prefix 128
+            in6prefix_copy_from_data(&prefix_information->prefix, &icmp_buf[scan_offset], 16);
+            break;
+        case icmp_option_route_information:
+            route_information = &option->option.route_information;
+
+            // route length 8
+            if (!dns_u8_parse(icmp_buf, length, &scan_offset, &route_information->length)) {
+                return false;
+            }
+            switch(option_length) {
+            case 8:
+                prefix_bytes = 0;
+                break;
+            case 16:
+                prefix_bytes = 8;
+                break;
+            case 24:
+                prefix_bytes = 16;
+                break;
+            default:
+                ERROR("invalid route information option length %d for route length %d",
+                      option_length, route_information->length);
+                return false;
+            }
+            // flags 8
+            if (!dns_u8_parse(icmp_buf, length, &scan_offset, &route_information->flags)) {
+                return false;
+            }
+            // route lifetime 32
+            if (!dns_u32_parse(icmp_buf, length, &scan_offset, &route_information->route_lifetime)) {
+                return false;
+            }
+            // route (64, 96 or 128)
+            in6prefix_copy_from_data(&route_information->prefix, &icmp_buf[scan_offset], prefix_bytes);
+            break;
+        case icmp_option_ra_flags_extension:
+            // The RA Flags extension as defined in RFC 5175 must have a length of 1 (meaning 8 bytes).
+            // It's possible that a later spec will define a length > 1, but since we are implementing
+            // RFC5175, we are required to silently ignore anything after the first 8 bytes. Since
+            // we've already checked for length=0 (invalid), we can just take our six bytes of flags
+            // and not bounds-check further.
+            memcpy(option->option.ra_flags_extension, &icmp_buf[scan_offset], sizeof(option->option.ra_flags_extension));
+            break;
+        default:
+        case icmp_option_mtu:
+        case icmp_option_redirected_header:
+            // don't care
+            break;
+        }
+        *offset += option_length;
+        option++;
+    }
+    return true;
+}
+
+
+static void
+icmp_message(route_state_t *route_state, uint8_t *icmp_buf, unsigned length, int ifindex, int hop_limit, addr_t *src, addr_t *dest)
+{
+    unsigned offset = 0;
+    uint32_t reserved32;
+    interface_t *interface;
+    icmp_message_t *message = calloc(1, sizeof(*message));
+    if (message == NULL) {
+        ERROR("Unable to allocate icmp_message_t for parsing");
+        return;
+    }
+
+    message->source = src->sin6.sin6_addr;
+    message->destination = dest->sin6.sin6_addr;
+    message->hop_limit = hop_limit;
+    for (interface = route_state->interfaces; interface; interface = interface->next) {
+        if (interface->index == ifindex) {
+            message->interface = interface;
+            break;
+        }
+    }
+    message->received_time = ioloop_timenow();
+    message->received_time_already_adjusted = false;
+    message->new_router = true;
+    message->route_state = route_state;
+
+    if (message->interface == NULL) {
+        SEGMENTED_IPv6_ADDR_GEN_SRP(message->source.s6_addr, src_buf);
+        SEGMENTED_IPv6_ADDR_GEN_SRP(message->destination.s6_addr, dst_buf);
+        INFO("ICMP message type %d from " PRI_SEGMENTED_IPv6_ADDR_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP
+             " on interface index %d, which isn't listed.",
+             icmp_buf[0], SEGMENTED_IPv6_ADDR_PARAM_SRP(message->source.s6_addr, src_buf),
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(message->destination.s6_addr, dst_buf), ifindex);
+        icmp_message_free(message);
+        return;
+    }
+
+    if (length < sizeof (struct icmp6_hdr)) {
+        ERROR("Short ICMP message: length %d is shorter than ICMP header length %zd", length, sizeof(struct icmp6_hdr));
+        icmp_message_free(message);
+        return;
+    }
+    INFO("length %d", length);
+
+    // The increasingly innaccurately named dns parse functions will work fine for this.
+    if (!dns_u8_parse(icmp_buf, length, &offset, &message->type)) {
+        goto out;
+    }
+    if (!dns_u8_parse(icmp_buf, length, &offset, &message->code)) {
+        goto out;
+    }
+    // XXX check the checksum
+    if (!dns_u16_parse(icmp_buf, length, &offset, &message->checksum)) {
+        goto out;
+    }
+    switch(message->type) {
+    case icmp_type_router_advertisement:
+        if (!dns_u8_parse(icmp_buf, length, &offset, &message->cur_hop_limit)) {
+            goto out;
+        }
+        if (!dns_u8_parse(icmp_buf, length, &offset, &message->flags)) {
+            goto out;
+        }
+        if (!dns_u16_parse(icmp_buf, length, &offset, &message->router_lifetime)) {
+            goto out;
+        }
+        if (!dns_u32_parse(icmp_buf, length, &offset, &message->reachable_time)) {
+            goto out;
+        }
+        if (!dns_u32_parse(icmp_buf, length, &offset, &message->retransmission_timer)) {
+            goto out;
+        }
+
+        if (!icmp_message_parse_options(message, icmp_buf, length, &offset)) {
+            goto out;
+        }
+        icmp_message_dump(message, &message->source, &message->destination);
+        router_advertisement(message);
+        // router_advertisement() is given ownership of the message
+        return;
+
+    case icmp_type_router_solicitation:
+        if (!dns_u32_parse(icmp_buf, length, &offset, &reserved32)) {
+            goto out;
+        }
+        if (!icmp_message_parse_options(message, icmp_buf, length, &offset)) {
+            goto out;
+        }
+        icmp_message_dump(message, &message->source, &message->destination);
+        router_solicit(message);
+        // router_solicit() is given ownership of the message.
+        return;
+
+    case icmp_type_neighbor_advertisement:
+        icmp_message_dump(message, &message->source, &message->destination);
+        neighbor_advertisement(message);
+        break;
+
+    case icmp_type_neighbor_solicitation:
+    case icmp_type_echo_request:
+    case icmp_type_echo_reply:
+    case icmp_type_redirect:
+        break;
+    }
+
+out:
+    icmp_message_free(message);
+    return;
+}
+
+#ifndef FUZZING
+static
+#endif
+void
+icmp_callback(io_t *NONNULL io, void *UNUSED context)
+{
+    ssize_t rv;
+    uint8_t icmp_buf[1500];
+    int ifindex = 0;
+    addr_t src, dest;
+    int hop_limit = 0;
+
+#ifndef FUZZING
+    rv = ioloop_recvmsg(io->fd, &icmp_buf[0], sizeof(icmp_buf), &ifindex, &hop_limit, &src, &dest);
+#else
+    rv = read(io->fd, &icmp_buf, sizeof(icmp_buf));
+#endif
+    if (rv < 0) {
+        ERROR("icmp_callback: can't read ICMP message: " PUB_S_SRP, strerror(errno));
+        return;
+    }
+    for (route_state_t *route_state = route_states; route_state != NULL; route_state = route_state->next) {
+        icmp_message(route_state, icmp_buf, (unsigned)rv, ifindex, hop_limit, &src, &dest); // rv will never be > sizeof(icmp_buf)
+    }
+}
+
+static void
+route_information_to_wire(dns_towire_state_t *towire, void *prefix_data,
+                          const char *source_interface, const char *dest_interface)
+{
+    uint8_t *prefix = prefix_data;
+
+#ifndef ND_OPT_ROUTE_INFORMATION
+#define ND_OPT_ROUTE_INFORMATION 24
+#endif
+    dns_u8_to_wire(towire, ND_OPT_ROUTE_INFORMATION);
+    dns_u8_to_wire(towire, 2); // length / 8
+    dns_u8_to_wire(towire, 64); // Interface prefixes are always 64 bits
+    dns_u8_to_wire(towire, 0); // There's no reason at present to prefer one Thread BR over another
+    dns_u32_to_wire(towire, BR_PREFIX_LIFETIME); // Route lifetime 1800 seconds (30 minutes)
+    dns_rdata_raw_data_to_wire(towire, prefix, 8); // /64 requires 8 bytes.
+    SEGMENTED_IPv6_ADDR_GEN_SRP(prefix, thread_prefix_buf);
+    INFO("Sending route to " PRI_SEGMENTED_IPv6_ADDR_SRP "%%" PUB_S_SRP " on " PUB_S_SRP,
+         SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix, thread_prefix_buf), source_interface, dest_interface);
+}
+
+static void
+icmp_send(uint8_t *message, size_t length, interface_t *interface, const struct in6_addr *destination)
+{
+#ifdef FUZZING
+    char buffer[length];
+    memcpy(buffer, message, length);
+    return;
+#endif
+    struct iovec iov;
+    struct in6_pktinfo *packet_info;
+    socklen_t cmsg_length = CMSG_SPACE(sizeof(*packet_info)) + CMSG_SPACE(sizeof (int));
+    uint8_t *cmsg_buffer;
+    struct msghdr msg_header;
+    struct cmsghdr *cmsg_pointer;
+    int hop_limit = 255;
+    ssize_t rv;
+    struct sockaddr_in6 dest;
+
+    // Make space for the control message buffer.
+    cmsg_buffer = calloc(1, cmsg_length);
+    if (cmsg_buffer == NULL) {
+        ERROR("Unable to construct ICMP Router Advertisement: no memory");
+        return;
+    }
+
+    // Send the message
+    memset(&dest, 0, sizeof(dest));
+    dest.sin6_family = AF_INET6;
+    dest.sin6_scope_id = interface->index;
+#ifndef NOT_HAVE_SA_LEN
+    dest.sin6_len = sizeof(dest);
+#endif
+    msg_header.msg_namelen = sizeof(dest);
+    dest.sin6_addr = *destination;
+
+    msg_header.msg_name = &dest;
+    iov.iov_base = message;
+    iov.iov_len = length;
+    msg_header.msg_iov = &iov;
+    msg_header.msg_iovlen = 1;
+    msg_header.msg_control = cmsg_buffer;
+    msg_header.msg_controllen = cmsg_length;
+
+    // Specify the interface
+    cmsg_pointer = CMSG_FIRSTHDR(&msg_header);
+    cmsg_pointer->cmsg_level = IPPROTO_IPV6;
+    cmsg_pointer->cmsg_type = IPV6_PKTINFO;
+    cmsg_pointer->cmsg_len = CMSG_LEN(sizeof(*packet_info));
+    packet_info = (struct in6_pktinfo *)CMSG_DATA(cmsg_pointer);
+    memset(packet_info, 0, sizeof(*packet_info));
+    packet_info->ipi6_ifindex = interface->index;
+
+    // Router advertisements and solicitations have a hop limit of 255
+    cmsg_pointer = CMSG_NXTHDR(&msg_header, cmsg_pointer);
+    cmsg_pointer->cmsg_level = IPPROTO_IPV6;
+    cmsg_pointer->cmsg_type = IPV6_HOPLIMIT;
+    cmsg_pointer->cmsg_len = CMSG_LEN(sizeof(int));
+    memcpy(CMSG_DATA(cmsg_pointer), &hop_limit, sizeof(hop_limit));
+
+
+    // Send it
+    rv = sendmsg(icmp_listener.io_state->fd, &msg_header, 0);
+    if (rv < 0) {
+        uint8_t *in6_addr_bytes = ((struct sockaddr_in6 *)(msg_header.msg_name))->sin6_addr.s6_addr;
+        SEGMENTED_IPv6_ADDR_GEN_SRP(in6_addr_bytes, in6_addr_buf);
+        ERROR("icmp_send: sending " PUB_S_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP " on interface " PUB_S_SRP
+              " index %d: " PUB_S_SRP, message[0] == ND_ROUTER_SOLICIT ? "solicit" : "advertise",
+              SEGMENTED_IPv6_ADDR_PARAM_SRP(in6_addr_bytes, in6_addr_buf),
+              interface->name, interface->index, strerror(errno));
+    } else if ((size_t)rv != iov.iov_len) {
+        ERROR("icmp_send: short send to interface " PUB_S_SRP ": %zd < %zd", interface->name, rv, iov.iov_len);
+    }
+    free(cmsg_buffer);
+}
+
+void
+router_advertisement_send(interface_t *interface, const struct in6_addr *destination)
+{
+    uint8_t *message;
+    dns_towire_state_t towire;
+    route_state_t *route_state = interface->route_state;
+
+    // Thread blocks RAs so no point sending them.
+    if (interface->inactive
+#ifndef RA_TESTER
+        || interface->is_thread
+#endif
+        ) {
+        return;
+    }
+
+#define MAX_ICMP_MESSAGE 1280
+    message = malloc(MAX_ICMP_MESSAGE);
+    if (message == NULL) {
+        ERROR("router_advertisement_send: unable to construct ICMP Router Advertisement: no memory");
+        return;
+    }
+
+    // Construct the ICMP header and options for each interface.
+    memset(&towire, 0, sizeof towire);
+    towire.p = message;
+    towire.lim = message + MAX_ICMP_MESSAGE;
+
+    // Construct the ICMP header.
+    // We use the DNS message construction functions because it's easy; probably should just make
+    // the towire functions more generic.
+    dns_u8_to_wire(&towire, ND_ROUTER_ADVERT);  // icmp6_type
+    dns_u8_to_wire(&towire, 0);                 // icmp6_code
+    dns_u16_to_wire(&towire, 0);                // The kernel computes the checksum (we don't technically have it).
+    dns_u8_to_wire(&towire, 0);                 // Hop limit, we don't set.
+    dns_u8_to_wire(&towire, 0);                 // Flags.  We don't offer DHCP, so We set neither the M nor the O bit.
+    // We are not a home agent, so no H bit.  Lifetime is 0, so Prf is 0.
+#ifdef ROUTER_LIFETIME_HACK
+    dns_u16_to_wire(&towire, BR_PREFIX_LIFETIME); // Router lifetime, hacked.  This shouldn't ever be enabled.
+#else
+#ifdef RA_TESTER
+    // Advertise a default route on the simulated thread network
+    if (!strcmp(interface->name, route_state->thread_interface_name)) {
+        dns_u16_to_wire(&towire, BR_PREFIX_LIFETIME); // Router lifetime for default route
+    } else {
+#endif
+        dns_u16_to_wire(&towire, 0);            // Router lifetime for non-default default route(s).
+#ifdef RA_TESTER
+    }
+#endif // RA_TESTER
+#endif // ROUTER_LIFETIME_HACK
+    dns_u32_to_wire(&towire, 0);                // Reachable time for NUD, we have no opinion on this.
+    dns_u32_to_wire(&towire, 0);                // Retransmission timer, again we have no opinion.
+
+#ifndef RA_TESTER
+    // Send MTU of 1280 for Thread?
+    if (interface->is_thread) {
+        dns_u8_to_wire(&towire, ND_OPT_MTU);
+        dns_u8_to_wire(&towire, 1); // length / 8
+        dns_u32_to_wire(&towire, 1280);
+        INFO("advertising MTU of 1280 on " PUB_S_SRP, interface->name);
+    }
+#endif
+
+    // Send Prefix Information option if there's no IPv6 on the link.
+    if (interface->our_prefix_advertised && !interface->suppress_ipv6_prefix && route_state->have_xpanid_prefix) {
+        dns_u8_to_wire(&towire, ND_OPT_PREFIX_INFORMATION);
+        dns_u8_to_wire(&towire, 4); // length / 8
+        dns_u8_to_wire(&towire, 64); // On-link prefix is always 64 bits
+        dns_u8_to_wire(&towire, ND_OPT_PI_FLAG_ONLINK | ND_OPT_PI_FLAG_AUTO); // On link, autoconfig
+        dns_u32_to_wire(&towire, interface->valid_lifetime);
+        dns_u32_to_wire(&towire, interface->preferred_lifetime);
+        dns_u32_to_wire(&towire, 0); // Reserved
+        dns_rdata_raw_data_to_wire(&towire, &interface->ipv6_prefix, sizeof interface->ipv6_prefix);
+        SEGMENTED_IPv6_ADDR_GEN_SRP(interface->ipv6_prefix.s6_addr, ipv6_prefix_buf);
+        INFO("advertising on-link prefix " PRI_SEGMENTED_IPv6_ADDR_SRP " on " PUB_S_SRP,
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(interface->ipv6_prefix.s6_addr, ipv6_prefix_buf), interface->name);
+
+    }
+
+    // In principle we can either send routes to links that are reachable by this router,
+    // or just advertise a router to the entire ULA /48.   In theory it doesn't matter
+    // which we do; if we support HNCP at some point we probably need to be specific, but
+    // for now being general is fine because we have no way to share a ULA.
+    // Unfortunately, some RIO implementations do not work with specific routes, so for now
+    // We are doing it the easy way and just advertising the /48.
+#define SEND_INTERFACE_SPECIFIC_RIOS 1
+#ifdef SEND_INTERFACE_SPECIFIC_RIOS
+
+    // If neither ROUTE_BETWEEN_NON_THREAD_LINKS nor RA_TESTER are defined, then we never want to
+    // send an RIO other than for the thread network prefix.
+#if defined (ROUTE_BETWEEN_NON_THREAD_LINKS) || defined(RA_TESTER)
+    interface_t *ifroute;
+    // Send Route Information option for other interfaces.
+    for (ifroute = route_state->interfaces; ifroute; ifroute = ifroute->next) {
+        if (ifroute->inactive) {
+            continue;
+        }
+        if (want_routing(route_state) &&
+            ifroute->our_prefix_advertised &&
+#ifdef SEND_ON_LINK_ROUTE
+            // In theory we don't want to send RIO for the on-link prefix, but there's this bug, see.
+            true &&
+#else
+            ifroute != interface &&
+#endif
+#ifdef RA_TESTER
+            // For the RA tester, we don't need to send an RIO to the thread network because we're the
+            // default router for that network.
+            strcmp(interface->name, route_state->thread_interface_name)
+#else
+            true
+#endif
+            )
+        {
+            route_information_to_wire(&towire, &ifroute->ipv6_prefix, ifroute->name, interface->name);
+        }
+    }
+#endif // ROUTE_BETWEEN_NON_THREAD_LINKS || RA_TESTER
+
+#ifndef RA_TESTER
+    // Send route information option for thread prefix
+    if (route_state->omr_watcher != NULL) {
+        omr_prefix_t *thread_prefixes = omr_watcher_prefixes_get(route_state->omr_watcher);
+
+        // Send RIOs for any other prefixes that appear on the Thread network
+        for (struct omr_prefix *prefix = thread_prefixes; prefix != NULL; prefix = prefix->next) {
+            route_information_to_wire(&towire, &prefix->prefix, route_state->thread_interface_name, interface->name);
+        }
+    }
+#endif
+#else
+#ifndef SKIP_SLASH_48
+    dns_u8_to_wire(&towire, ND_OPT_ROUTE_INFORMATION);
+    dns_u8_to_wire(&towire, 3); // length / 8
+    dns_u8_to_wire(&towire, 48); // ULA prefixes are always 48 bits
+    dns_u8_to_wire(&towire, 0); // There's no reason at present to prefer one Thread BR over another
+    dns_u32_to_wire(&towire, BR_PREFIX_LIFETIME); // Route lifetime 1800 seconds (30 minutes)
+    dns_rdata_raw_data_to_wire(&towire, &route_state->srp_server->ula_prefix, 16); // /48 requires 16 bytes
+#endif // SKIP_SLASH_48
+#endif // SEND_INTERFACE_SPECIFIC_RIOS
+
+    // Send the stub router flag
+    dns_u8_to_wire(&towire, ND_OPT_RA_FLAGS_EXTENSION);
+    dns_u8_to_wire(&towire, 1); // length / 8
+    dns_u8_to_wire(&towire, RA_FLAGS1_STUB_ROUTER);
+    dns_u8_to_wire(&towire, 0); // Five bytes of zero flag bits
+    dns_u32_to_wire(&towire, 0);
+
+    // Send Source link-layer address option
+    if (interface->have_link_layer_address) {
+        dns_u8_to_wire(&towire, ND_OPT_SOURCE_LINKADDR);
+        dns_u8_to_wire(&towire, 1); // length / 8
+        dns_rdata_raw_data_to_wire(&towire, &interface->link_layer, sizeof(interface->link_layer));
+    }
+
+    if (towire.error) {
+        ERROR("No space in ICMP output buffer for " PUB_S_SRP " at route.c:%d", interface->name, towire.line);
+        towire.error = 0;
+    } else {
+        SEGMENTED_IPv6_ADDR_GEN_SRP(destination->s6_addr, destination_buf);
+        INFO("sending advertisement to " PRI_SEGMENTED_IPv6_ADDR_SRP " on " PUB_S_SRP,
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(destination->s6_addr, destination_buf),
+             interface->name);
+        icmp_send(message, towire.p - message, interface, destination);
+    }
+    free(message);
+}
+
+void
+router_solicit_send(interface_t *interface)
+{
+    uint8_t *message;
+    dns_towire_state_t towire;
+
+    // Thread blocks RSs so no point sending them.
+    if (interface->inactive
+#ifndef RA_TESTER
+        || interface->is_thread
+#endif
+        ) {
+        return;
+    }
+#define MAX_ICMP_MESSAGE 1280
+    message = malloc(MAX_ICMP_MESSAGE);
+    if (message == NULL) {
+        ERROR("Unable to construct ICMP Router Advertisement: no memory");
+        return;
+    }
+
+    // Construct the ICMP header and options for each interface.
+    memset(&towire, 0, sizeof towire);
+    towire.p = message;
+    towire.lim = message + MAX_ICMP_MESSAGE;
+
+    // Construct the ICMP header.
+    // We use the DNS message construction functions because it's easy; probably should just make
+    // the towire functions more generic.
+    dns_u8_to_wire(&towire, ND_ROUTER_SOLICIT);  // icmp6_type
+    dns_u8_to_wire(&towire, 0);                  // icmp6_code
+    dns_u16_to_wire(&towire, 0);                 // The kernel computes the checksum (we don't technically have it).
+    dns_u32_to_wire(&towire, 0);                 // Reserved32
+
+    // Send Source link-layer address option
+    if (interface->have_link_layer_address) {
+        dns_u8_to_wire(&towire, ND_OPT_SOURCE_LINKADDR);
+        dns_u8_to_wire(&towire, 1); // length / 8
+        dns_rdata_raw_data_to_wire(&towire, &interface->link_layer, sizeof(interface->link_layer));
+    }
+
+    if (towire.error) {
+        ERROR("No space in ICMP output buffer for " PUB_S_SRP " at route.c:%d", interface->name, towire.line);
+    } else {
+        if (interface->have_link_layer_address) {
+            INFO("sending router solicit on " PUB_S_SRP " to all routers with source " PRI_MAC_ADDR_SRP,
+                 interface->name, MAC_ADDR_PARAM_SRP(interface->link_layer));
+        } else {
+            INFO("sending router solicit on " PUB_S_SRP " to all routers", interface->name);
+        }
+        icmp_send(message, towire.p - message, interface, &in6addr_linklocal_allrouters);
+    }
+    free(message);
+}
+
+void
+neighbor_solicit_send(interface_t *interface, struct in6_addr *destination)
+{
+    uint8_t *message;
+    dns_towire_state_t towire;
+
+#define MAX_ICMP_MESSAGE 1280
+    message = malloc(MAX_ICMP_MESSAGE);
+    if (message == NULL) {
+        ERROR("Unable to construct ICMP Router Advertisement: no memory");
+        return;
+    }
+
+    // Construct the ICMP header and options for each interface.
+    memset(&towire, 0, sizeof towire);
+    towire.p = message;
+    towire.lim = message + MAX_ICMP_MESSAGE;
+
+    // Construct the ICMP header.
+    // We use the DNS message construction functions because it's easy; probably should just make
+    // the towire functions more generic.
+    dns_u8_to_wire(&towire, ND_NEIGHBOR_SOLICIT);  // icmp6_type
+    dns_u8_to_wire(&towire, 0);                    // icmp6_code
+    dns_u16_to_wire(&towire, 0);                   // The kernel computes the checksum (we don't technically have it).
+    dns_u32_to_wire(&towire, 0);                   // Reserved32
+    dns_rdata_raw_data_to_wire(&towire, destination, sizeof(*destination)); // Target address of solicit
+
+    // Send Source link-layer address option
+    if (interface->have_link_layer_address) {
+        dns_u8_to_wire(&towire, ND_OPT_SOURCE_LINKADDR);
+        dns_u8_to_wire(&towire, 1); // length / 8
+        dns_rdata_raw_data_to_wire(&towire, &interface->link_layer, sizeof(interface->link_layer));
+    }
+
+    if (towire.error) {
+        ERROR("No space in ICMP output buffer for " PUB_S_SRP " at route.c:%d", interface->name, towire.line);
+    } else {
+        SEGMENTED_IPv6_ADDR_GEN_SRP(destination, dest_buf);
+        if (interface->have_link_layer_address) {
+            INFO("sending neighbor solicit on " PUB_S_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP " with source " PRI_MAC_ADDR_SRP,
+                 interface->name, SEGMENTED_IPv6_ADDR_PARAM_SRP(destination, dest_buf), MAC_ADDR_PARAM_SRP(interface->link_layer));
+        } else {
+            INFO("sending neighbor solicit on " PUB_S_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP,
+                 interface->name, SEGMENTED_IPv6_ADDR_PARAM_SRP(destination, dest_buf));
+        }
+        icmp_send(message, towire.p - message, interface, destination);
+    }
+    free(message);
+}
+
+bool
+start_icmp_listener(void)
+{
+#ifndef SRP_TEST_SERVER
+    int sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
+    int true_flag = 1;
+#ifdef CONFIGURE_STATIC_INTERFACE_ADDRESSES
+    int false_flag = 0;
+#endif
+    struct icmp6_filter filter;
+    ssize_t rv;
+
+    if (sock < 0) {
+        ERROR("Unable to listen for icmp messages: " PUB_S_SRP, strerror(errno));
+        close(sock);
+        return false;
+    }
+
+    // Only accept router advertisements and router solicits.
+    ICMP6_FILTER_SETBLOCKALL(&filter);
+    ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter);
+    ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter);
+    ICMP6_FILTER_SETPASS(ND_NEIGHBOR_ADVERT, &filter);
+    rv = setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter));
+    if (rv < 0) {
+        ERROR("Can't set IPV6_RECVHOPLIMIT: " PUB_S_SRP ".", strerror(errno));
+        close(sock);
+        return false;
+    }
+
+    // We want a source address and interface index
+    rv = setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &true_flag, sizeof(true_flag));
+    if (rv < 0) {
+        ERROR("Can't set IPV6_RECVPKTINFO: " PUB_S_SRP ".", strerror(errno));
+        close(sock);
+        return false;
+    }
+
+    // We need to be able to reject RAs arriving from off-link.
+    rv = setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &true_flag, sizeof(true_flag));
+    if (rv < 0) {
+        ERROR("Can't set IPV6_RECVHOPLIMIT: " PUB_S_SRP ".", strerror(errno));
+        close(sock);
+        return false;
+    }
+
+#ifdef CONFIGURE_STATIC_INTERFACE_ADDRESSES
+    // Prevent our router advertisements from updating our routing table.
+    rv = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &false_flag, sizeof(false_flag));
+    if (rv < 0) {
+        ERROR("Can't set IPV6_RECVHOPLIMIT: " PUB_S_SRP ".", strerror(errno));
+        close(sock);
+        return false;
+    }
+#endif
+
+    icmp_listener.io_state = ioloop_file_descriptor_create(sock, NULL, NULL);
+    if (icmp_listener.io_state == NULL) {
+        ERROR("No memory for ICMP I/O structure.");
+        close(sock);
+        return false;
+    }
+
+    // Beacon out a router advertisement every three minutes.
+    icmp_listener.unsolicited_interval = 3 * 60 * 1000;
+    ioloop_add_reader(icmp_listener.io_state, icmp_callback);
+#else
+    (void)icmp_callback;
+#endif // !SRP_TEST_SERVER
+
+    return true;
+}
+
+void
+icmp_interface_subscribe(interface_t *interface, bool added)
+{
+    struct ipv6_mreq req;
+    int rv;
+
+    if (icmp_listener.io_state == NULL) {
+        ERROR("Interface subscribe without ICMP listener.");
+        return;
+    }
+
+    memset(&req, 0, sizeof req);
+    if (interface->index == -1) {
+        ERROR("icmp_interface_subscribe called before interface index fetch for " PUB_S_SRP, interface->name);
+        return;
+    }
+
+    req.ipv6mr_multiaddr = in6addr_linklocal_allrouters;
+    req.ipv6mr_interface = interface->index;
+    rv = setsockopt(icmp_listener.io_state->fd, IPPROTO_IPV6, added ? IPV6_JOIN_GROUP : IPV6_LEAVE_GROUP, &req,
+                    sizeof req);
+    if (rv < 0) {
+        ERROR("Unable to " PUB_S_SRP " all-routers multicast group on " PUB_S_SRP ": " PUB_S_SRP,
+              added ? "join" : "leave", interface->name, strerror(errno));
+        return;
+    } else {
+        INFO(PUB_S_SRP "subscribed on interface " PUB_S_SRP, added ? "" : "un",
+             interface->name);
+    }
+
+    req.ipv6mr_multiaddr = in6addr_linklocal_allnodes;
+    req.ipv6mr_interface = interface->index;
+    rv = setsockopt(icmp_listener.io_state->fd, IPPROTO_IPV6, added ? IPV6_JOIN_GROUP : IPV6_LEAVE_GROUP, &req,
+                    sizeof req);
+    if (rv < 0) {
+        ERROR("Unable to " PUB_S_SRP " all-nodes multicast group on " PUB_S_SRP ": " PUB_S_SRP,
+              added ? "join" : "leave", interface->name, strerror(errno));
+        return;
+    } else {
+        INFO(PUB_S_SRP "subscribed on interface " PUB_S_SRP, added ? "" : "un",
+             interface->name);
+    }
+
+}
+
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 120
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/icmp.h b/ServiceRegistration/icmp.h
new file mode 100644
index 0000000..715ef8b
--- /dev/null
+++ b/ServiceRegistration/icmp.h
@@ -0,0 +1,46 @@
+/* icmp.h
+ *
+ * Copyright (c) 2019-2023 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This code contains global declarations relating to ICMP I/O functions and data structures for the Thread border
+ * router.
+ */
+
+struct icmp_listener {
+    io_t *NULLABLE io_state;
+    int sock;
+    uint32_t unsolicited_interval;
+};
+extern icmp_listener_t icmp_listener;
+
+void icmp_message_free(icmp_message_t *NONNULL message);
+void neighbor_solicit_send(interface_t *NONNULL interface, struct in6_addr *NONNULL destination);
+void icmp_message_dump(icmp_message_t *NONNULL message,
+                       struct in6_addr *NONNULL source_address, struct in6_addr *NONNULL destination_address);
+void set_router_mode(interface_t *NONNULL interface, int mode);
+void icmp_interface_subscribe(interface_t *NONNULL interface, bool added);
+bool start_icmp_listener(void);
+void neighbor_solicit_send(interface_t *NONNULL interface, struct in6_addr *NONNULL destination);
+void router_solicit_send(interface_t *NONNULL interface);
+void router_advertisement_send(interface_t *NONNULL interface, const struct in6_addr *NONNULL destination);
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 120
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/ifpermit.c b/ServiceRegistration/ifpermit.c
new file mode 100644
index 0000000..246c30f
--- /dev/null
+++ b/ServiceRegistration/ifpermit.c
@@ -0,0 +1,198 @@
+/* ifpermit.c
+ *
+ * Copyright (c) 2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Implementation of a permitted interface list object, which maintains a list of
+ * interfaces on which we are permitted to provide some service.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <pwd.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <time.h>
+#include <dns_sd.h>
+#include <net/if.h>
+#include <inttypes.h>
+#include <sys/resource.h>
+#include <netinet/icmp6.h>
+#include "srp.h"
+#include "ifpermit.h"
+#include "dns-msg.h"
+#include "ioloop.h"
+#include "srp-mdns-proxy.h"
+
+// If we aren't able to allocate a permitted interface list, we still need to return a non-NULL value so that we don't
+// fail open. So all of these functions need to treat this particular value as special but not dereference it.
+#define PERMITTED_INTERFACE_LIST_BLOCKED (ifpermit_list_t *)1
+
+typedef struct ifpermit_name ifpermit_name_t;
+struct ifpermit_name {
+    ifpermit_name_t *next;
+    char *name; // Interface name
+    uint32_t ifindex; // Interface index
+    int count;        // Number of permittors for this interface
+};
+
+struct ifpermit_list {
+	int ref_count;
+    ifpermit_name_t *names;
+};
+
+void
+ifpermit_list_add(ifpermit_list_t *permits, const char *name)
+{
+	if (permits == PERMITTED_INTERFACE_LIST_BLOCKED) {
+		ERROR("blocked permit list when adding " PUB_S_SRP, name);
+        return;
+	}
+    ifpermit_name_t **pname = &permits->names;
+    ifpermit_name_t *permit_name;
+    while (*pname != NULL) {
+        permit_name = *pname;
+        if (!strcmp(name, permit_name->name)) {
+        success:
+            permit_name->count++;
+            INFO("%d permits for interface " PUB_S_SRP " with index %d", permit_name->count, name, permit_name->ifindex);
+            return;
+        }
+        pname = &permit_name->next;
+    }
+    permit_name = calloc(1, sizeof(*permit_name));
+    if (permit_name != NULL) {
+        permit_name->name = strdup(name);
+        if (permit_name->name == NULL) {
+            free(permit_name);
+            permit_name = NULL;
+        } else {
+            permit_name->ifindex = if_nametoindex(name);
+            if (permit_name->ifindex == 0) {
+                ERROR("if_nametoindex for interface " PUB_S_SRP " returned 0.", name);
+                free(permit_name->name);
+                free(permit_name);
+                return;
+            }
+            *pname = permit_name;
+            goto success;
+        }
+    }
+    ERROR("no memory to add permit for " PUB_S_SRP, name);
+}
+
+void
+ifpermit_list_remove(ifpermit_list_t *permits, const char *name)
+{
+	if (permits == PERMITTED_INTERFACE_LIST_BLOCKED) {
+		ERROR("blocked permit list when removing " PUB_S_SRP, name);
+        return;
+    }
+    ifpermit_name_t **pname = &permits->names;
+    ifpermit_name_t *permit_name;
+    while (*pname != NULL) {
+        permit_name = *pname;
+        if (!strcmp(name, permit_name->name)) {
+            permit_name->count--;
+            INFO("%d permits for interface " PUB_S_SRP " with index %d", permit_name->count, name, permit_name->ifindex);
+            if (permit_name->count == 0) {
+                *pname = permit_name->next;
+                free(permit_name->name);
+                free(permit_name);
+            }
+            return;
+        }
+        pname = &permit_name->next;
+    }
+
+    FAULT("permit remove for interface " PUB_S_SRP " which does not exist", name);
+}
+
+static void
+ifpermit_list_finalize(ifpermit_list_t *list)
+{
+    if (list != NULL && list != PERMITTED_INTERFACE_LIST_BLOCKED) {
+        ifpermit_name_t *names = list->names, *next = NULL;
+        while (names != NULL) {
+            next = names->next;
+            free(names->name);
+            free(names);
+            names = next;
+        }
+        free(list);
+    }
+}
+
+void
+ifpermit_list_retain_(ifpermit_list_t *list, const char *file, int line)
+{
+    if (list != NULL && list != PERMITTED_INTERFACE_LIST_BLOCKED) {
+        RETAIN(list, ifpermit_list);
+    }
+}
+
+void
+ifpermit_list_release_(ifpermit_list_t *list, const char *file, int line)
+{
+    if (list != NULL && list != PERMITTED_INTERFACE_LIST_BLOCKED) {
+        RELEASE(list, ifpermit_list);
+    }
+}
+
+ifpermit_list_t *
+ifpermit_list_create_(const char *file, int line)
+{
+    ifpermit_list_t *permits = calloc(1, sizeof(*permits));
+    if (permits == NULL) {
+        return PERMITTED_INTERFACE_LIST_BLOCKED;
+    }
+    RETAIN(permits, ifpermit_list);
+    return permits;
+}
+
+bool
+ifpermit_interface_is_permitted(ifpermit_list_t *permits, uint32_t ifindex)
+{
+    if (permits != NULL && permits != PERMITTED_INTERFACE_LIST_BLOCKED) {
+        for (ifpermit_name_t *name = permits->names; name != NULL; name = name->next) {
+            if (name->ifindex == ifindex) {
+                return true;
+            }
+        }
+    }
+    return false;
+}
+
+void ifpermit_add_permitted_interface_to_server_(srp_server_t *NONNULL server_state, const char *NONNULL name,
+                                                 const char *file, int line)
+{
+    if (server_state->permitted_interfaces == NULL) {
+        server_state->permitted_interfaces = ifpermit_list_create_(file, line);
+    }
+    ifpermit_list_add(server_state->permitted_interfaces, name);
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 120
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/ifpermit.h b/ServiceRegistration/ifpermit.h
new file mode 100644
index 0000000..15253df
--- /dev/null
+++ b/ServiceRegistration/ifpermit.h
@@ -0,0 +1,48 @@
+/* thread-device.h
+ *
+ * Copyright (c) 2023 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Definitions for a permitted interface list object, which maintains a list of
+ * interfaces on which we are permitted to provide some service.
+ */
+
+#ifndef __IFPERMIT_H__
+#define __IFPERMIT_H__ 1
+
+typedef struct ifpermit_list ifpermit_list_t;
+typedef struct srp_server_state srp_server_t;
+#define ifpermit_list_create() ifpermit_list_create_(__FILE__, __LINE__)
+ifpermit_list_t *NULLABLE ifpermit_list_create_(const char *NONNULL file, int line);
+void ifpermit_list_add(ifpermit_list_t *NONNULL list, const char *NONNULL name);
+void ifpermit_list_remove(ifpermit_list_t *NONNULL list, const char *NONNULL name);
+#define ifpermit_list_retain(list) ifpermit_list_retain_(list, __FILE__, __LINE__)
+void ifpermit_list_retain_(ifpermit_list_t *NULLABLE list, const char *NONNULL file, int line);
+#define ifpermit_list_release(list) ifpermit_list_release_(list, __FILE__, __LINE__)
+void ifpermit_list_release_(ifpermit_list_t *NULLABLE list, const char *NONNULL file, int line);
+bool ifpermit_interface_is_permitted(ifpermit_list_t *NULLABLE permits, uint32_t ifindex);
+#define ifpermit_add_permitted_interface_to_server(server_state, name) \
+    ifpermit_add_permitted_interface_to_server_(server_state, name, __FILE__, __LINE__)
+void ifpermit_add_permitted_interface_to_server_(srp_server_t *NONNULL server_state, const char *NONNULL name,
+                                                 const char *NONNULL file, int line);
+#endif // __IFPERMIT_H__
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 120
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/ioloop.c b/ServiceRegistration/ioloop.c
index 585330b..31579c0 100644
--- a/ServiceRegistration/ioloop.c
+++ b/ServiceRegistration/ioloop.c
@@ -49,6 +49,7 @@
 #ifndef EXCLUDE_TLS
 #include "srp-tls.h"
 #endif
+#include "ifpermit.h"
 
 #ifndef IOLOOP_MACOS
 
@@ -554,6 +555,20 @@
 }
 #endif // !defined(IOLOOP_MACOS)
 
+static void
+ioloop_normalize_address(addr_t *normalized, addr_t *original)
+{
+    uint16_t *sinp = (uint16_t *)&original->sin6.sin6_addr;
+    // Check for ::ffff:xxxx:xxxx, which is an ipv4mapped address
+    if (sinp[0] == 0 && sinp[1] == 0 && sinp[2] == 0 && sinp[3] == 0 && sinp[4] == 0 && sinp[5] == 0xffff) {
+        normalized->sin.sin_family = AF_INET;
+        memcpy(&normalized->sin.sin_addr, &sinp[6], sizeof(struct in_addr));
+        normalized->sin.sin_port = original->sin6.sin6_port;
+    } else {
+        *normalized = *original;
+    }
+}
+
 void
 ioloop_udp_read_callback(io_t *io, void *context)
 {
@@ -597,7 +612,10 @@
 
     // For UDP, we use the interface index as part of the validation strategy, so go get
     // the interface index.
+    bool set_local = false;
     for (cmh = CMSG_FIRSTHDR(&msg); cmh; cmh = CMSG_NXTHDR(&msg, cmh)) {
+        addr_t source_address, local_address;
+
         if (cmh->cmsg_level == IPPROTO_IPV6 && cmh->cmsg_type == IPV6_PKTINFO) {
             struct in6_pktinfo pktinfo;
 
@@ -611,14 +629,7 @@
 #ifndef NOT_HAVE_SA_LEN
             message->local.sin6.sin6_len = sizeof message->local;
 #endif
-            SEGMENTED_IPv6_ADDR_GEN_SRP(&src.sin6.sin6_addr, src_addr_buf);
-            SEGMENTED_IPv6_ADDR_GEN_SRP(&message->local.sin6.sin6_addr, dest_addr_buf);
-                INFO("received %zd byte UDP message on index %d to " PRI_SEGMENTED_IPv6_ADDR_SRP "#%d from "
-                     PRI_SEGMENTED_IPv6_ADDR_SRP "#%d", rv, message->ifindex,
-                     SEGMENTED_IPv6_ADDR_PARAM_SRP(&message->local.sin6.sin6_addr,  dest_addr_buf),
-                     ntohs(message->local.sin6.sin6_port),
-                     SEGMENTED_IPv6_ADDR_PARAM_SRP(&src.sin6.sin6_addr, src_addr_buf),
-                     ntohs(src.sin6.sin6_port));
+            set_local = true;
         } else if (cmh->cmsg_level == IPPROTO_IP && cmh->cmsg_type == IP_PKTINFO) {
             struct in_pktinfo pktinfo;
 
@@ -631,16 +642,39 @@
             message->local.sin.sin_len = sizeof message->local;
 #endif
             message->local.sin.sin_port = htons(connection->listen_port);
-            IPv4_ADDR_GEN_SRP(&src.sin.sin_addr.s_addr, src_addr_buf);
-            IPv4_ADDR_GEN_SRP(&message->local.sin.sin_addr.s_addr, dest_addr_buf);
-            INFO("received %zd byte UDP message on index %d to " PRI_IPv4_ADDR_SRP "#%d from " PRI_IPv4_ADDR_SRP "#%d", rv,
-                 message->ifindex, IPv4_ADDR_PARAM_SRP(&message->local.sin.sin_addr.s_addr, dest_addr_buf),
-                 ntohs(message->local.sin.sin_port),
-                 IPv4_ADDR_PARAM_SRP(&src.sin.sin_addr.s_addr, src_addr_buf),
-                 ntohs(src.sin.sin_port));
+            set_local = true;
+        }
+        if (set_local) {
+            ioloop_normalize_address(&source_address, &src);
+            ioloop_normalize_address(&local_address, &message->local);
+            if (source_address.sa.sa_family == AF_INET6) {
+                SEGMENTED_IPv6_ADDR_GEN_SRP(&source_address.sin6.sin6_addr, src_addr_buf);
+                SEGMENTED_IPv6_ADDR_GEN_SRP(&local_address.sin6.sin6_addr, dest_addr_buf);
+                INFO("received %zd byte UDP message on index %d to " PRI_SEGMENTED_IPv6_ADDR_SRP "#%d from "
+                     PRI_SEGMENTED_IPv6_ADDR_SRP "#%d", rv, message->ifindex,
+                     SEGMENTED_IPv6_ADDR_PARAM_SRP(&local_address.sin6.sin6_addr,  dest_addr_buf),
+                     ntohs(local_address.sin6.sin6_port),
+                     SEGMENTED_IPv6_ADDR_PARAM_SRP(&source_address.sin6.sin6_addr, src_addr_buf),
+                     ntohs(source_address.sin6.sin6_port));
+            } else {
+                IPv4_ADDR_GEN_SRP(&source_address.sin.sin_addr.s_addr, src_addr_buf);
+                IPv4_ADDR_GEN_SRP(&local_address.sin.sin_addr.s_addr, dest_addr_buf);
+                INFO("received %zd byte UDP message on index %d to " PRI_IPv4_ADDR_SRP "#%d from " PRI_IPv4_ADDR_SRP "#%d", rv,
+                     message->ifindex, IPv4_ADDR_PARAM_SRP(&local_address.sin.sin_addr.s_addr, dest_addr_buf),
+                     ntohs(local_address.sin.sin_port),
+                     IPv4_ADDR_PARAM_SRP(&local_address.sin.sin_addr.s_addr, src_addr_buf),
+                     ntohs(source_address.sin.sin_port));
+            }
         }
     }
-    connection->datagram_callback(connection, message, connection->context);
+
+    // The first packet we get via inetd will not have the PKTINFO sockopt set, since we can only set that after we've
+    // started. We can expect a retransmission, so just drop it rather than trying to do something clever.
+    if (set_local) {
+        connection->datagram_callback(connection, message, connection->context);
+    } else {
+        ERROR("dropping incoming packet because we didn't get a destination address.");
+    }
     ioloop_message_release(message);
 }
 
@@ -820,7 +854,7 @@
     mh.msg_iovlen = iov_len;
     mh.msg_name = dest;
     mh.msg_control = cmsg_buf;
-    if (source == NULL && ifindex == 0) {
+    if (source == NULL) {
         mh.msg_controllen = 0;
     } else {
         mh.msg_controllen = sizeof cmsg_buf;
@@ -836,13 +870,8 @@
             inp = (struct in_pktinfo *)CMSG_DATA(cmsg);
             memset(inp, 0, sizeof *inp);
             inp->ipi_ifindex = ifindex;
-            if (source) {
-                IPv4_ADDR_GEN_SRP(&source->sin.sin_addr.s_addr, ipv4_addr_buf);
-                INFO("sending UDP response from " PRI_IPv4_ADDR_SRP "#%d",
-                     IPv4_ADDR_PARAM_SRP(&source->sin.sin_addr.s_addr, ipv4_addr_buf), ntohs(source->sin.sin_port));
-                inp->ipi_spec_dst = source->sin.sin_addr;
-                inp->ipi_addr = source->sin.sin_addr;
-            }
+            inp->ipi_spec_dst = source->sin.sin_addr;
+            inp->ipi_addr = source->sin.sin_addr;
         } else if (source->sa.sa_family == AF_INET6) {
             struct in6_pktinfo *inp;
             mh.msg_namelen = sizeof (struct sockaddr_in6);
@@ -853,13 +882,7 @@
             inp = (struct in6_pktinfo *)CMSG_DATA(cmsg);
             memset(inp, 0, sizeof *inp);
             inp->ipi6_ifindex = ifindex;
-            if (source) {
-                SEGMENTED_IPv6_ADDR_GEN_SRP(&source->sin6.sin6_addr.s6_addr, ipv6_addr_buf);
-                INFO("sending UDP response from " PRI_SEGMENTED_IPv6_ADDR_SRP "#%d",
-                     SEGMENTED_IPv6_ADDR_PARAM_SRP(&source->sin6.sin6_addr.s6_addr, ipv6_addr_buf),
-                     ntohs(source->sin6.sin6_port));
-                inp->ipi6_addr = source->sin6.sin6_addr;
-            }
+            inp->ipi6_addr = source->sin6.sin6_addr;
         } else {
             ERROR("unknown family %d", source->sa.sa_family);
             abort();
@@ -869,16 +892,30 @@
     for (int i = 0; i < iov_len; i++) {
         len += iov[i].iov_len;
     }
-    if (dest->sa.sa_family == AF_INET) {
-        IPv4_ADDR_GEN_SRP(&dest->sin.sin_addr.s_addr, ipv4_addr_buf);
-            INFO("sending %zd byte UDP response to " PRI_IPv4_ADDR_SRP "#%d", len,
-                 IPv4_ADDR_PARAM_SRP(&dest->sin.sin_addr.s_addr, ipv4_addr_buf),
-                 ntohs(dest->sin.sin_port));
+    addr_t dest_addr, source_addr;
+    ioloop_normalize_address(&dest_addr, dest);
+    if (source != NULL) {
+        ioloop_normalize_address(&source_addr, source);
     } else {
-        SEGMENTED_IPv6_ADDR_GEN_SRP(&dest->sin6.sin6_addr.s6_addr, ipv6_addr_buf);
-        INFO("sending %zd byte UDP response to " PRI_SEGMENTED_IPv6_ADDR_SRP "#%d", len,
-             SEGMENTED_IPv6_ADDR_PARAM_SRP(&dest->sin6.sin6_addr.s6_addr, ipv6_addr_buf),
-             ntohs(dest->sin6.sin6_port));
+        memset(&source_addr, 0, sizeof(source_addr));
+        source_addr.sa.sa_family = dest_addr.sa.sa_family;
+    }
+    if (dest_addr.sa.sa_family == AF_INET) {
+        IPv4_ADDR_GEN_SRP(&source_addr.sin.sin_addr.s_addr, ipv4_src_buf);
+        IPv4_ADDR_GEN_SRP(&dest_addr.sin.sin_addr.s_addr, ipv4_dest_buf);
+        INFO("sending %zd byte UDP response from " PRI_IPv4_ADDR_SRP " port %d index %d to " PRI_IPv4_ADDR_SRP "#%d",
+             len, IPv4_ADDR_PARAM_SRP(&source_addr.sin.sin_addr.s_addr, ipv4_src_buf),
+             ifindex, ntohs(source_addr.sin.sin_port),
+             IPv4_ADDR_PARAM_SRP(&dest_addr.sin.sin_addr.s_addr, ipv4_dest_buf), ntohs(dest_addr.sin.sin_port));
+    } else {
+        SEGMENTED_IPv6_ADDR_GEN_SRP(&source_addr.sin6.sin6_addr.s6_addr, ipv6_src_buf);
+        SEGMENTED_IPv6_ADDR_GEN_SRP(&dest_addr.sin6.sin6_addr.s6_addr, ipv6_dest_buf);
+        INFO("sending %zd byte UDP response from "
+             PRI_SEGMENTED_IPv6_ADDR_SRP " port %d index %d to " PRI_SEGMENTED_IPv6_ADDR_SRP "#%d",
+             len, SEGMENTED_IPv6_ADDR_PARAM_SRP(&source_addr.sin6.sin6_addr.s6_addr, ipv6_src_buf),
+             ntohs(source_addr.sin6.sin6_port), ifindex,
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(&dest_addr.sin6.sin6_addr.s6_addr, ipv6_dest_buf),
+             ntohs(dest_addr.sin6.sin6_port));
     }
     status = sendmsg(comm->io.fd, &mh, 0);
     if (status < 0) {
@@ -1131,11 +1168,11 @@
 }
 
 comm_t *
-ioloop_listener_create(bool stream, bool tls, uint16_t *UNUSED avoid_ports, int UNUSED num_avoid_ports,
+ioloop_listener_create(bool stream, bool tls, bool inetd, uint16_t *UNUSED avoid_ports, int UNUSED num_avoid_ports,
                        const addr_t *ip_address, const char *multicast, const char *name,
                        datagram_callback_t datagram_callback, connect_callback_t connected,
                        cancel_callback_t UNUSED cancel, ready_callback_t ready, finalize_callback_t finalize,
-                       tls_config_callback_t UNUSED tls_config, void *context)
+                       tls_config_callback_t UNUSED tls_config, unsigned UNUSED ifindex, void *context)
 {
     comm_t *listener;
     socklen_t sl;
@@ -1859,11 +1896,10 @@
 }
 
 const struct sockaddr *
-connection_get_local_address(comm_t *connection)
+connection_get_local_address(message_t *message)
 {
-    message_t *message = connection->message;
     if (message == NULL) {
-        ERROR("message is NULL.")
+        ERROR("message is NULL.");
         return NULL;
     }
     return &message->local.sa;
diff --git a/ServiceRegistration/ioloop.h b/ServiceRegistration/ioloop.h
index 991bd90..2f6e39c 100644
--- a/ServiceRegistration/ioloop.h
+++ b/ServiceRegistration/ioloop.h
@@ -1,6 +1,6 @@
 /* ioloop.h
  *
- * Copyright (c) 2018-2021 Apple Inc. All rights reserved.
+ * Copyright (c) 2018-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -64,7 +64,6 @@
         int index;
         uint8_t addr[8];
     } ether_addr;
-    struct sockaddr_un sun;
 };
 
 #define IOLOOP_NTOP(addr, buf) \
@@ -94,12 +93,13 @@
 typedef struct wakeup wakeup_t;
 typedef struct dnssd_txn dnssd_txn_t;
 typedef struct interface_address_state interface_address_state_t;
+typedef struct ifpermit_list ifpermit_list_t;
 
 typedef void (*dnssd_txn_finalize_callback_t)(void *NONNULL context);
 typedef void (*dnssd_txn_failure_callback_t)(void *NONNULL context, int status);
 typedef void (*wakeup_callback_t)(void *NONNULL context);
 typedef void (*finalize_callback_t)(void *NONNULL context);
-typedef void (*cancel_callback_t)(void *NONNULL context);
+typedef void (*cancel_callback_t)(comm_t *NONNULL comm, void *NONNULL context);
 typedef void (*ready_callback_t)(void *NONNULL context, uint16_t port);
 typedef void (*io_callback_t)(io_t *NONNULL io, void *NONNULL context);
 typedef void (*comm_callback_t)(comm_t *NONNULL comm);
@@ -107,9 +107,11 @@
 typedef void (*connect_callback_t)(comm_t *NONNULL connection, void *NULLABLE context);
 typedef void (*disconnect_callback_t)(comm_t *NONNULL comm, void *NULLABLE context, int error);
 enum interface_address_change { interface_address_added, interface_address_deleted, interface_address_unchanged };
-typedef void (*interface_callback_t)(void *NULLABLE context, const char *NONNULL name,
-                                     const addr_t *NONNULL address, const addr_t *NONNULL netmask,
-                                     uint32_t flags, enum interface_address_change event_type);
+typedef struct srp_server_state srp_server_t;
+typedef void (*interface_callback_t)(srp_server_t *NULLABLE server_state, void *NULLABLE context,
+                                     const char *NONNULL name, const addr_t *NONNULL address,
+                                     const addr_t *NONNULL netmask, uint32_t flags,
+                                     enum interface_address_change event_type);
 typedef void (*subproc_callback_t)(void *NULLABLE context, int status, const char *NULLABLE error);
 typedef void (*tls_config_callback_t)(void *NONNULL context);
 typedef void (*async_callback_t)(void *NULLABLE context);
@@ -189,9 +191,16 @@
     uint16_t listen_port;
     uint16_t *NULLABLE avoid_ports;
     int num_avoid_ports;
+    int serial;
     bool avoiding;
     char *NONNULL name;
     void *NULLABLE context;
+#ifdef SRP_TEST_SERVER
+    void *NULLABLE test_context;
+    bool (*NULLABLE test_send_intercept)(comm_t *NONNULL connection, message_t *NULLABLE responding_to,
+                                         struct iovec *NONNULL iov, int iov_len, bool final, bool send_length);
+    void *NULLABLE srp_server;
+#endif
     datagram_callback_t NULLABLE datagram_callback;
     comm_callback_t NULLABLE close_callback;
     connect_callback_t NULLABLE connected;
@@ -202,10 +211,12 @@
     uint8_t *NULLABLE buf;
     dso_state_t *NULLABLE dso;
     tls_context_t *NULLABLE tls_context;
-    addr_t address, multicast;
+    addr_t address, multicast, local;
     size_t message_length_len;
     size_t message_length, message_cur;
     uint8_t message_length_bytes[2];
+
+
 #ifdef IOLOOP_MACOS
     bool read_pending: 1; // Only ever one.
     bool server: 1;       // Indicates that this connection was created by a listener
@@ -220,6 +231,7 @@
     bool is_connected: 1;
     bool is_listener: 1;
     bool opportunistic: 1;
+    bool canceled: 1;
 };
 
 #define MAX_SUBPROC_ARGS 20
@@ -265,6 +277,9 @@
 #define ioloop_wakeup_retain(wakeup) ioloop_wakeup_retain_(wakeup, __FILE__, __LINE__)
 void ioloop_wakeup_retain_(wakeup_t *NONNULL wakeup, const char *NONNULL file, int line);
 #define ioloop_wakeup_release(wakeup) ioloop_wakeup_release_(wakeup, __FILE__, __LINE__)
+#ifndef ioloop_wakeup_forget
+    #define ioloop_wakeup_forget(ptr) _SRP_STRICT_DISPOSE_TEMPLATE(ptr, ioloop_wakeup_release)
+#endif
 void ioloop_wakeup_release_(wakeup_t *NONNULL wakeup, const char *NONNULL file, int line);
 bool ioloop_add_wake_event(wakeup_t *NONNULL wakeup, void *NULLABLE context,
                            wakeup_callback_t NONNULL callback, finalize_callback_t NULLABLE finalize,
@@ -288,12 +303,14 @@
 #define ioloop_listener_release(wakeup) ioloop_listener_release_(wakeup, __FILE__, __LINE__)
 void ioloop_listener_release_(comm_t *NONNULL listener, const char *NONNULL file, int line);
 void ioloop_listener_cancel(comm_t *NONNULL comm);
-comm_t *NULLABLE ioloop_listener_create(bool stream, bool tls, uint16_t *NULLABLE avoid_ports, int num_avoid_ports,
-                                        const addr_t *NULLABLE ip_address, const char *NULLABLE multicast,
-                                        const char *NONNULL name, datagram_callback_t NONNULL datagram_callback,
+comm_t *NULLABLE ioloop_listener_create(bool stream, bool tls, bool init_daemon, uint16_t *NULLABLE avoid_ports,
+                                        int num_avoid_ports, const addr_t *NULLABLE ip_address,
+                                        const char *NULLABLE multicast, const char *NONNULL name,
+                                        datagram_callback_t NONNULL datagram_callback,
                                         connect_callback_t NULLABLE connected, cancel_callback_t NULLABLE cancel,
                                         ready_callback_t NULLABLE ready, finalize_callback_t NULLABLE finalize,
-                                        tls_config_callback_t NULLABLE tls_config, void *NULLABLE context);
+                                        tls_config_callback_t NULLABLE tls_config, unsigned ifindex,
+                                        void *NULLABLE context);
 comm_t *NULLABLE ioloop_connection_create(addr_t *NONNULL remote_address, bool tls, bool stream, bool stable,
                                           bool opportunistic, datagram_callback_t NONNULL datagram_callback,
                                           connect_callback_t NULLABLE connected,
@@ -317,11 +334,14 @@
                             struct iovec *NONNULL iov, int iov_len);
 void ioloop_dump_object_allocation_stats(void);
 void ioloop_strcpy(char *NONNULL dest, const char *NONNULL src, size_t lim);
-bool ioloop_map_interface_addresses(const char *NULLABLE ifname, void *NULLABLE context, interface_callback_t NULLABLE callback);
-#define ioloop_map_interface_addresses_here(here, ifname, context, callback) \
-    ioloop_map_interface_addresses_here_(here, ifname, context, callback, __FILE__, __LINE__)
-bool ioloop_map_interface_addresses_here_(interface_address_state_t *NONNULL *NULLABLE here,
-                                          const char *NULLABLE ifname, void *NULLABLE context, interface_callback_t NULLABLE callback,
+bool ioloop_map_interface_addresses(srp_server_t *NULLABLE server_state,
+                                    const char *NULLABLE ifname, void *NULLABLE context, interface_callback_t NULLABLE callback);
+#define ioloop_map_interface_addresses_here(server_state, here, ifname, context, callback) \
+    ioloop_map_interface_addresses_here_(server_state, here, ifname, context, callback, __FILE__, __LINE__)
+bool ioloop_map_interface_addresses_here_(srp_server_t *NULLABLE server_state,
+                                          interface_address_state_t *NONNULL *NULLABLE here,
+                                          const char *NULLABLE ifname,
+                                          void *NULLABLE context, interface_callback_t NULLABLE callback,
                                           const char *NONNULL file, int line);
 ssize_t ioloop_recvmsg(int sock, uint8_t *NONNULL buffer, size_t buffer_length, int *NONNULL ifindex,
                        int *NONNULL hoplimit, addr_t *NONNULL source, addr_t *NONNULL destination);
@@ -349,6 +369,9 @@
 #define ioloop_dnssd_txn_retain(txn) ioloop_dnssd_txn_retain_(txn, __FILE__, __LINE__)
 void ioloop_dnssd_txn_retain_(dnssd_txn_t *NONNULL txn, const char *NONNULL file, int line);
 #define ioloop_dnssd_txn_release(txn) ioloop_dnssd_txn_release_(txn, __FILE__, __LINE__)
+#ifndef ioloop_dnssd_txn_forget
+    #define ioloop_dnssd_txn_forget(ptr) _SRP_STRICT_DISPOSE_TEMPLATE(ptr, ioloop_dnssd_txn_release)
+#endif
 void ioloop_dnssd_txn_release_(dnssd_txn_t *NONNULL txn, const char *NONNULL file, int line);
 #endif
 void ioloop_dnssd_txn_set_aux_pointer(dnssd_txn_t *NONNULL txn, void *NULLABLE aux_pointer);
@@ -375,15 +398,19 @@
 bool srp_store_file_data(void *NULLABLE host_context, const char *NONNULL filename, uint8_t *NONNULL buffer,
                          uint16_t length);
 time_t srp_time(void);
+double srp_fractional_time(void);
+int64_t srp_utime(void);
 void srp_format_time_offset(char *NONNULL buf, size_t buf_len, time_t offset);
 
-const struct sockaddr *NULLABLE connection_get_local_address(comm_t *NULLABLE connection);
+const struct sockaddr *NULLABLE connection_get_local_address(message_t *NULLABLE message);
 
 #if !UDP_LISTENER_USES_CONNECTION_GROUPS
 bool ioloop_udp_send_message(comm_t *NONNULL comm, addr_t *NULLABLE source, addr_t *NONNULL dest, int ifindex,
                              struct iovec *NONNULL iov, int iov_len);
 void ioloop_udp_read_callback(io_t *NONNULL io, void *NULLABLE context);
 #endif
+int get_num_fds(void);
+
 
 // Local Variables:
 // mode: C
diff --git a/ServiceRegistration/macos-ioloop.c b/ServiceRegistration/macos-ioloop.c
index 066f70a..13926b3 100644
--- a/ServiceRegistration/macos-ioloop.c
+++ b/ServiceRegistration/macos-ioloop.c
@@ -1,6 +1,6 @@
 /* macos-ioloop.c
  *
- * Copyright (c) 2018-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2018-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -36,6 +36,7 @@
 #include <ifaddrs.h>
 #include <dns_sd.h>
 
+#include <CoreUtils/SystemUtils.h>  // For `IsAppleTV()`.
 #include <dispatch/dispatch.h>
 
 #include "srp.h"
@@ -44,14 +45,19 @@
 #include "ioloop.h"
 #include "tls-macos.h"
 #include "tls-keychain.h"
+#include "srp-dnssd.h"
+#include "ifpermit.h"
 
 dispatch_queue_t ioloop_main_queue;
+static int cur_connection_serial;
 
 // Forward references
 static void ioloop_tcp_input_start(comm_t *NONNULL connection);
 static void listener_finalize(comm_t *listener);
 static bool connection_write_now(comm_t *NONNULL connection);
-static void ioloop_read_cancel(void *context);
+static bool ioloop_listener_connection_ready(comm_t *connection);
+
+#define DSCP_CS5 0x28
 
 int
 getipaddr(addr_t *addr, const char *p)
@@ -227,15 +233,18 @@
     return 0;
 }
 
-#define connection_cancel(conn) connection_cancel_(conn, __FILE__, __LINE__)
+#define connection_cancel(comm, conn) connection_cancel_(comm, conn, __FILE__, __LINE__)
 static void
-connection_cancel_(nw_connection_t connection, const char *file, int line)
+connection_cancel_(comm_t *comm, nw_connection_t connection, const char *file, int line)
 {
     if (connection == NULL) {
         INFO("null connection at " PUB_S_SRP ":%d", file, line);
     } else {
-        INFO("%p: " PUB_S_SRP ":%d", connection, file, line);
-        nw_connection_cancel(connection);
+        INFO("%p: " PUB_S_SRP " " PUB_S_SRP ":%d" , connection, comm->canceled ? " (already canceled)" : "", file, line);
+        if (!comm->canceled) {
+            nw_connection_cancel(connection);
+            comm->canceled = true;
+        }
     }
 }
 
@@ -314,7 +323,7 @@
 {
     if (connection->connection != NULL) {
         INFO("%p %p", connection, connection->connection);
-        connection_cancel(connection->connection);
+        connection_cancel(connection, connection->connection);
 #if UDP_LISTENER_USES_CONNECTION_GROUPS
     } else if (connection->connection_group != NULL) {
         INFO("%p %p", connection, connection->connection_group);
@@ -325,13 +334,11 @@
         int fd = connection->io.fd;
         if (fd != -1) {
             ioloop_close(&connection->io);
-            close(fd);
-            ioloop_read_cancel(&connection->io);
             if (connection->cancel != NULL) {
                 RETAIN_HERE(connection, listener);
                 dispatch_async(ioloop_main_queue, ^{
                         if (connection->cancel != NULL) {
-                            connection->cancel(connection->context);
+                            connection->cancel(connection, connection->context);
                         }
                         RELEASE_HERE(connection, listener);
                     });
@@ -394,6 +401,12 @@
     int i;
     uint16_t len = 0;
 
+#ifdef SRP_TEST_SERVER
+    if (connection->test_send_intercept != NULL) {
+        return connection->test_send_intercept(connection, responding_to, iov, iov_len, final, send_length);
+    }
+#endif
+
     // Not needed on OSX because UDP conversations are treated as "connections."
 #if UDP_LISTENER_USES_CONNECTION_GROUPS
     (void)responding_to;
@@ -548,7 +561,7 @@
                                if (error != NULL) {
                                    ERROR("ioloop_send_message: write failed: " PUB_S_SRP,
                                          strerror(nw_error_get_error_code(error)));
-                                   connection_cancel(connection->connection);
+                                   connection_cancel(connection, connection->connection);
                                }
                                if (connection->writes_pending > 0) {
                                    connection->writes_pending--;
@@ -571,18 +584,18 @@
     bool ret = true, *retp = &ret;
 
     if (error != NULL) {
-        ERROR("datagram_read: " PUB_S_SRP, strerror(nw_error_get_error_code(error)));
+        ERROR(PUB_S_SRP, strerror(nw_error_get_error_code(error)));
         ret = false;
         goto out;
     }
     if (length > UINT16_MAX) {
-        ERROR("datagram_read: oversized datagram length %zd", length);
+        ERROR("oversized datagram length %zd", length);
         ret = false;
         goto out;
     }
     message = ioloop_message_create(length);
     if (message == NULL) {
-        ERROR("datagram_read: unable to allocate message.");
+        ERROR("unable to allocate message.");
         ret = false;
         goto out;
     }
@@ -590,7 +603,7 @@
     dispatch_data_apply(content,
                         ^bool (dispatch_data_t __unused region, size_t offset, const void *buffer, size_t size) {
             if (message->length < offset + size) {
-                ERROR("datagram_read: data region %zd:%zd is out of range for message length %d",
+                ERROR("data region %zd:%zd is out of range for message length %d",
                       offset, size, message->length);
                 *retp = false;
                 return false;
@@ -599,6 +612,37 @@
             return true;
         });
     if (ret == true) {
+        // Set the local address
+        message->local = connection->local;
+
+#ifdef HEXDUMP_INCOMING_DATAGRAMS
+        uint16_t length = message->length > 8192 ? 8192 : message->length; // Don't dump really big messages
+        for (uint16_t i = 0; i < length; i += 32) {
+            char obuf[256];
+            char *obp = obuf;
+            int left = sizeof(obp) - 1;
+            uint16_t max = message->length - i;
+            if (max > 32) {
+                max = 32;
+            }
+            for (uint16_t j = 0; j < max && left > 0; j += 8) {
+                uint16_t submax = max - j;
+                if (submax > 8) {
+                    submax = 8;
+                }
+                for (uint16_t k = 0; k < submax; k++) {
+                    snprintf(obp, left, "%02x", ((uint8_t *)&message->wire)[i + j + k]);
+                    obp += 2;
+                    *obp++ = ' ';
+                    left -= 3;
+                }
+                *obp++ = ' ';
+                left--;
+            }
+            *obp = 0;
+            INFO("%03d " PUB_S_SRP, i, obuf);
+        }
+#endif
         // Process the message.
         if (connection->listener_state != NULL) {
             connection->listener_state->datagram_callback(connection, message, connection->listener_state->context);
@@ -612,7 +656,7 @@
         ioloop_message_release(message);
     }
     if (!ret && connection->connection != NULL) {
-        connection_cancel(connection->connection);
+        connection_cancel(connection, connection->connection);
     }
     return ret;
 }
@@ -660,7 +704,7 @@
     }
     if (fail) {
         if (connection->connection != NULL) {
-            connection_cancel(connection->connection);
+            connection_cancel(connection, connection->connection);
         }
     }
     return fail;
@@ -693,7 +737,7 @@
     map = dispatch_data_create_map(content, (const void **)&lenbuf, &length);
     if (map == NULL) {
         ERROR("tcp_read_length: map create failed");
-        connection_cancel(connection->connection);
+        connection_cancel(connection, connection->connection);
         return;
     }
     dispatch_release(map);
@@ -727,13 +771,13 @@
     // For TCP connections, is_complete means the other end closed the connection.
     if (connection->tcp_stream && is_complete) {
         INFO("remote end closed connection.");
-        connection_cancel(connection->connection);
+        connection_cancel(connection, connection->connection);
         return true;
     }
 
     if (content == NULL) {
         INFO("remote end closed connection.");
-        connection_cancel(connection->connection);
+        connection_cancel(connection, connection->connection);
         return true;
     }
     return false;
@@ -774,14 +818,20 @@
 }
 
 static void
-connection_state_changed(comm_t *connection, nw_connection_state_t state, nw_error_t error)
+ioloop_connection_state_changed(comm_t *connection, nw_connection_state_t state, nw_error_t error)
 {
     char errbuf[512];
     connection_error_to_string(error, errbuf, sizeof(errbuf));
 
     if (state == nw_connection_state_ready) {
-        INFO(PRI_S_SRP " state is ready; error = " PUB_S_SRP,
-             connection->name != NULL ? connection->name : "<no name>", errbuf);
+        if (connection->server) {
+            if (!ioloop_listener_connection_ready(connection)) {
+                ioloop_comm_cancel(connection);
+                return;
+            }
+        }
+        INFO(PRI_S_SRP " (%p %p) state is ready; error = " PUB_S_SRP,
+             connection->name != NULL ? connection->name : "<no name>", connection, connection->connection, errbuf);
         // Set up a reader.
         if (connection->tcp_stream) {
             ioloop_tcp_input_start(connection);
@@ -799,13 +849,13 @@
     } else if (state == nw_connection_state_failed || state == nw_connection_state_waiting) {
         // Waiting is equivalent to failed because we are not giving libnetcore enough information to
         // actually succeed when there is a problem connecting (e.g. "EHOSTUNREACH").
-        INFO(PRI_S_SRP " state is " PUB_S_SRP "; error = " PUB_S_SRP,
-             state == nw_connection_state_failed ? "failed" : "waiting",
-             connection->name != NULL ? connection->name : "<no name>", errbuf);
-        connection_cancel(connection->connection);
+        INFO(PRI_S_SRP " (%p %p) state is " PUB_S_SRP "; error = " PUB_S_SRP,
+             connection->name != NULL ? connection->name : "<no name>", connection, connection->connection,
+             state == nw_connection_state_failed ? "failed" : "waiting", errbuf);
+        connection_cancel(connection, connection->connection);
     } else if (state == nw_connection_state_cancelled) {
-        INFO(PRI_S_SRP " state is canceled; error = " PUB_S_SRP,
-             connection->name != NULL ? connection->name : "<no name>", errbuf);
+        INFO(PRI_S_SRP " (%p %p) state is canceled; error = " PUB_S_SRP,
+             connection->name != NULL ? connection->name : "<no name>", connection, connection->connection, errbuf);
         if (connection->disconnected != NULL) {
             connection->disconnected(connection, connection->context, 0);
         }
@@ -814,15 +864,39 @@
     } else {
         if (error != NULL) {
             // We can get here if e.g. the TLS handshake fails.
-            nw_connection_cancel(connection->connection);
+            connection_cancel(connection, connection->connection);
         }
-        INFO(PRI_S_SRP " state is %d; error = " PUB_S_SRP,
-             connection->name != NULL ? connection->name : "<no name>", state, errbuf);
+        INFO(PRI_S_SRP " (%p %p) state is %d; error = " PUB_S_SRP,
+             connection->name != NULL ? connection->name : "<no name>", connection, connection->connection, state, errbuf);
     }
 }
 
 static void
-ioloop_connection_set_name_from_endpoint(comm_t *listener, comm_t *connection, nw_endpoint_t endpoint)
+ioloop_connection_get_address_from_endpoint(addr_t *addr, nw_endpoint_t endpoint)
+{
+    nw_endpoint_type_t endpoint_type = nw_endpoint_get_type(endpoint);
+    if (endpoint_type == nw_endpoint_type_address) {
+        char *address_string = nw_endpoint_copy_address_string(endpoint);
+        if (address_string == NULL) {
+            ERROR("unable to get description of new connection.");
+        } else {
+            getipaddr(addr, address_string);
+            if (addr->sa.sa_family == AF_INET6) {
+                SEGMENTED_IPv6_ADDR_GEN_SRP(&addr->sin6.sin6_addr, rdata_buf);
+                INFO("parsed connection local IPv6 address is: " PRI_SEGMENTED_IPv6_ADDR_SRP,
+                     SEGMENTED_IPv6_ADDR_PARAM_SRP(&addr->sin6.sin6_addr, rdata_buf));
+            } else {
+                IPv4_ADDR_GEN_SRP(&addr->sin.sin_addr, rdata_buf);
+                INFO("parsed connection local IPv4 address is: " PRI_IPv4_ADDR_SRP,
+                     IPv4_ADDR_PARAM_SRP(&addr->sin.sin_addr, rdata_buf));
+            }
+        }
+        free(address_string);
+    }
+}
+
+static void
+ioloop_connection_set_name_from_endpoint(comm_t *connection, nw_endpoint_t endpoint)
 {
     nw_endpoint_type_t endpoint_type = nw_endpoint_get_type(endpoint);
     if (endpoint_type == nw_endpoint_type_address) {
@@ -831,23 +905,33 @@
         if (port_string == NULL || address_string == NULL) {
             ERROR("Unable to get description of new connection.");
         } else {
-            asprintf(&connection->name, "%s connection from %s/%s", listener->name, address_string, port_string);
+            const char *listener_name = connection->name == NULL ? "bogus" : connection->name;
+            char *free_name = connection->name;
+            connection->name = NULL;
+            asprintf(&connection->name, "%s connection from %s/%s", listener_name, address_string, port_string);
+            if (free_name != NULL) {
+                free(free_name);
+                free_name = NULL;
+                listener_name = NULL;
+            }
             getipaddr(&connection->address, address_string);
             if (connection->address.sa.sa_family == AF_INET6) {
                 SEGMENTED_IPv6_ADDR_GEN_SRP(&connection->address.sin6.sin6_addr, rdata_buf);
-                INFO("parsed connection IPv6 address is: " PRI_SEGMENTED_IPv6_ADDR_SRP,
+                INFO("parsed connection remote IPv6 address is: " PRI_SEGMENTED_IPv6_ADDR_SRP,
                      SEGMENTED_IPv6_ADDR_PARAM_SRP(&connection->address.sin6.sin6_addr, rdata_buf));
             } else {
                 IPv4_ADDR_GEN_SRP(&connection->address.sin.sin_addr, rdata_buf);
-                INFO("parsed connection IPv4 address is: " PRI_IPv4_ADDR_SRP,
+                INFO("parsed connection remote IPv4 address is: " PRI_IPv4_ADDR_SRP,
                      IPv4_ADDR_PARAM_SRP(&connection->address.sin.sin_addr, rdata_buf));
             }
         }
         free(port_string);
         free(address_string);
     } else {
-        ERROR("incoming connection of unexpected type %d", endpoint_type);
-        connection->name = nw_connection_copy_description(connection->connection);
+        if (connection->name == NULL) {
+            connection->name = nw_connection_copy_description(connection->connection);
+        }
+        ERROR("incoming connection " PRI_S_SRP " is of unexpected type %d", connection->name, endpoint_type);
     }
 }
 
@@ -863,6 +947,7 @@
             ERROR("%p: " PRI_S_SRP ": no memory for response state.", listener, listener->name);
             return;
         }
+        response_state->serial = ++cur_connection_serial;
         RETAIN_HERE(response_state, comm);
         response_state->listener_state = listener;
         RETAIN_HERE(response_state->listener_state, listener);
@@ -879,23 +964,14 @@
 #else
 #endif
 
-static void
-connection_callback(comm_t *listener, nw_connection_t new_connection)
-{
-    comm_t *connection = calloc(1, sizeof *connection);
-    if (connection == NULL) {
-        ERROR("Unable to receive connection: no memory.");
-        nw_connection_cancel(new_connection);
-        return;
-    }
 
-    connection->connection = new_connection;
-    nw_retain(connection->connection);
-    nw_connection_created++;
+static bool
+ioloop_listener_connection_ready(comm_t *connection)
+{
 
     nw_endpoint_t endpoint = nw_connection_copy_endpoint(connection->connection);
     if (endpoint != NULL) {
-        ioloop_connection_set_name_from_endpoint(listener, connection, endpoint);
+        ioloop_connection_set_name_from_endpoint(connection, endpoint);
         nw_release(endpoint);
     }
     if (connection->name != NULL) {
@@ -905,19 +981,48 @@
         connection->name = strdup("unidentified");
     }
 
+    // Best effort
+    nw_endpoint_t local_endpoint = nw_connection_copy_connected_local_endpoint(connection->connection);
+    if (local_endpoint != NULL) {
+        ioloop_connection_get_address_from_endpoint(&connection->local, endpoint);
+        nw_release(local_endpoint);
+    }
+
+    if (connection->connected != NULL) {
+        connection->connected(connection, connection->context);
+    }
+    return true;
+}
+
+static void
+ioloop_listener_connection_callback(comm_t *listener, nw_connection_t new_connection)
+{
+    nw_connection_set_queue(new_connection, ioloop_main_queue);
+    nw_connection_start(new_connection);
+
+    comm_t *connection = calloc(1, sizeof *connection);
+    if (connection == NULL) {
+        ERROR("Unable to receive connection: no memory.");
+        nw_connection_cancel(new_connection);
+        return;
+    }
+    connection->serial = ++cur_connection_serial;
+
+    connection->connection = new_connection;
+    nw_retain(connection->connection);
+    nw_connection_created++;
+
+    connection->name = strdup(listener->name);
     connection->datagram_callback = listener->datagram_callback;
     connection->tcp_stream = listener->tcp_stream;
     connection->server = true;
     connection->context = listener->context;
+    connection->connected = listener->connected;
     RETAIN_HERE(connection, comm); // The connection state changed handler has a reference to the connection.
     nw_connection_set_state_changed_handler(connection->connection,
                                             ^(nw_connection_state_t state, nw_error_t error)
-                                            { connection_state_changed(connection, state, error); });
-    nw_connection_set_queue(connection->connection, ioloop_main_queue);
-    nw_connection_start(connection->connection);
-    if (listener->connected != NULL) {
-        listener->connected(connection, listener->context);
-    }
+                                            { ioloop_connection_state_changed(connection, state, error); });
+    INFO("started " PRI_S_SRP, connection->name);
 }
 
 static void
@@ -970,15 +1075,36 @@
 void
 ioloop_listener_cancel(comm_t *connection)
 {
+    // Only need to do it once.
+    if (connection->canceled) {
+        FAULT("cancel on canceled connection " PRI_S_SRP, connection->name);
+        return;
+    }
+    connection->canceled = true;
     if (connection->listener != NULL) {
         nw_listener_cancel(connection->listener);
         // connection->listener will be released in ioloop_listener_state_changed_handler: nw_listener_state_cancelled.
     }
 #if UDP_LISTENER_USES_CONNECTION_GROUPS
-    if (!connection->stream && connection->io.fd != -1) {
-        ioloop_close(&connection->io);
-        io->refcnt = 100; // Prevent read_cancel from finalizing the io object
-        ioloop_read_cancel(&connection->io);
+    if (connection->connection_group != NULL) {
+        INFO("%p %p", connection, connection->connection_group);
+        nw_connection_group_cancel(connection->connection_group);
+    }
+#else
+    if (!connection->tcp_stream && connection->connection == NULL) {
+        int fd = connection->io.fd;
+        if (fd != -1) {
+            ioloop_close(&connection->io);
+            if (connection->cancel != NULL) {
+                RETAIN_HERE(connection, listener);
+                dispatch_async(ioloop_main_queue, ^{
+                        if (connection->cancel != NULL) {
+                            connection->cancel(connection, connection->context);
+                        }
+                        RELEASE_HERE(connection, listener);
+                    });
+            }
+        }
     }
 #endif
 }
@@ -1023,6 +1149,12 @@
             INFO("failed");
             nw_connection_group_cancel(listener->connection_group);
         } else if (state == nw_connection_group_state_ready) {
+            // It's possible that we might schedule the ready event but then before we return to the run loop
+            // the listener gets canceled, in which case we don't want to deliver the ready event.
+            if (listener->canceled) {
+                INFO("ready but canceled");
+                return;
+            }
             INFO("ready");
             if (listener->avoiding) {
                 listener->listen_port = nw_connection_group_get_port(listener->connection_group);
@@ -1060,7 +1192,7 @@
                 ;
             cancel:
                 if (listener->cancel) {
-                    listener->cancel(listener->context);
+                    listener->cancel(listener, listener->context);
                 }
                 RELEASE_HERE(listener, listener);
             }
@@ -1084,6 +1216,8 @@
     }
 #endif // DEBUG_VERBOSE
 
+    INFO("%p %p " PUB_S_SRP " %d", listener, listener->listener, listener->name, state);
+
     // Should never happen.
     if (listener->listener == NULL && state != nw_listener_state_cancelled) {
         return;
@@ -1105,11 +1239,20 @@
             nw_listener_cancel(listener->listener);
         } else if (state == nw_listener_state_ready) {
             INFO("ready");
+            if (listener->ready != NULL) {
+                listener->ready(listener->context, listener->listen_port);
+            }
         } else if (state == nw_listener_state_cancelled) {
             INFO("cancelled");
             nw_release(listener->listener);
             nw_listener_finalized++;
             listener->listener = NULL;
+            if (listener->cancel != NULL) {
+                listener->cancel(listener, listener->context);
+            }
+            RELEASE_HERE(listener, listener); // Release the nw_listener handler function's reference to the ioloop listener object.
+        } else {
+            INFO("something else");
         }
     }
 }
@@ -1140,34 +1283,14 @@
 }
 #else
 static comm_t *
-ioloop_udp_listener_setup(comm_t *listener, const addr_t *ip_address, uint16_t port)
+ioloop_udp_listener_setup(comm_t *listener, const addr_t *ip_address, uint16_t port, const char *launchd_name, int ifindex)
 {
     sa_family_t family = (ip_address != NULL) ? ip_address->sa.sa_family : AF_UNSPEC;
     sa_family_t real_family = family == AF_UNSPEC ? AF_INET6 : family;
     int true_flag = 1;
     addr_t sockname;
-
-    listener->io.fd = socket(real_family, SOCK_DGRAM, IPPROTO_UDP);
-    if (listener->io.fd < 0) {
-        ERROR("Can't get socket: %s", strerror(errno));
-        goto out;
-    }
-    int rv = setsockopt(listener->io.fd, SOL_SOCKET, SO_REUSEADDR, &true_flag, sizeof true_flag);
-    if (rv < 0) {
-        ERROR("SO_REUSEADDR failed: %s", strerror(errno));
-        goto out;
-    }
-
-    rv = setsockopt(listener->io.fd, SOL_SOCKET, SO_REUSEPORT, &true_flag, sizeof true_flag);
-    if (rv < 0) {
-        ERROR("SO_REUSEPORT failed: %s", strerror(errno));
-        goto out;
-    }
-
-    if (fcntl(listener->io.fd, F_SETFL, O_NONBLOCK) < 0) {
-        ERROR("%s: Can't set O_NONBLOCK: %s", listener->name, strerror(errno));
-        goto out;
-    }
+    socklen_t sl;
+    int rv;
 
     listener->address.sa.sa_family = real_family;
     listener->address.sa.sa_len = (real_family == AF_INET
@@ -1179,53 +1302,128 @@
         listener->address.sin.sin_port = htons(port);
     }
 
-    // skipping multicast support for now
-
-    if (family == AF_INET6) {
-        // Don't use a dual-stack socket.
-        rv = setsockopt(listener->io.fd, IPPROTO_IPV6, IPV6_V6ONLY, &true_flag, sizeof true_flag);
-        if (rv < 0) {
-            SEGMENTED_IPv6_ADDR_GEN_SRP(listener->address.sin6.sin6_addr.s6_addr, ipv6_addr_buf);
-            ERROR("Unable to set IPv6-only flag on UDP socket for " PRI_SEGMENTED_IPv6_ADDR_SRP,
-                  SEGMENTED_IPv6_ADDR_PARAM_SRP(listener->address.sin6.sin6_addr.s6_addr, ipv6_addr_buf));
-            goto out;
-        }
-        SEGMENTED_IPv6_ADDR_GEN_SRP(listener->address.sin6.sin6_addr.s6_addr, ipv6_addr_buf);
-        ERROR("Successfully set IPv6-only flag on UDP socket for " PRI_SEGMENTED_IPv6_ADDR_SRP,
-              SEGMENTED_IPv6_ADDR_PARAM_SRP(listener->address.sin6.sin6_addr.s6_addr, ipv6_addr_buf));
-    }
-
-    socklen_t sl = listener->address.sa.sa_len;
-    if (bind(listener->io.fd, &listener->address.sa, sl) < 0) {
-        if (family == AF_INET) {
-            IPv4_ADDR_GEN_SRP(&listener->address.sin.sin_addr.s_addr, ipv4_addr_buf);
-            ERROR("Can't bind to " PRI_IPv4_ADDR_SRP "#%d: %s",
-                  IPv4_ADDR_PARAM_SRP(&listener->address.sin.sin_addr.s_addr, ipv4_addr_buf), ntohs(port),
-                  strerror(errno));
+    listener->io.fd = -1;
+#ifndef SRP_TEST_SERVER
+    if (launchd_name != NULL) {
+        int *fds;
+        size_t cnt;
+        int ret = launch_activate_socket(launchd_name, &fds, &cnt);
+        if (ret != 0) {
+            FAULT("launchd_activate_socket failed for " PUB_S_SRP ": " PUB_S_SRP, launchd_name, strerror(ret));
+            listener->io.fd = -1;
+        } else if (cnt == 0) {
+            FAULT("too few sockets returned from launchd_active_socket for " PUB_S_SRP" : %zd", launchd_name, cnt);
+            listener->io.fd = -1;
+        } else if (cnt != 1) {
+            FAULT("too many sockets returned from launchd_active_socket for " PUB_S_SRP" : %zd", launchd_name, cnt);
+            for (size_t i = 0; i < cnt; i++) {
+                close(fds[i]);
+            }
+            free(fds);
         } else {
-            SEGMENTED_IPv6_ADDR_GEN_SRP(&listener->address.sin6.sin6_addr.s6_addr, ipv6_addr_buf);
-            ERROR("Can't bind to " PRI_SEGMENTED_IPv6_ADDR_SRP "#%d: %s",
-                  SEGMENTED_IPv6_ADDR_PARAM_SRP(&listener->address.sin6.sin6_addr.s6_addr, ipv6_addr_buf), ntohs(port),
-                  strerror(errno));
+            listener->io.fd = fds[0];
+            free(fds);
         }
-    out:
-        close(listener->io.fd);
-        listener->io.fd = -1;
-        RELEASE_HERE(listener, listener);
-        return NULL;
     }
-
-    // We may have bound to an unspecified port, so fetch the port we got.
-    if (port == 0 && family != AF_LOCAL) {
-        if (getsockname(listener->io.fd, (struct sockaddr *)&sockname, &sl) < 0) {
-            ERROR("ioloop_listener_create: getsockname: %s", strerror(errno));
+#endif
+    if (listener->io.fd == -1) {
+        listener->io.fd = socket(real_family, SOCK_DGRAM, IPPROTO_UDP);
+        if (listener->io.fd < 0) {
+            ERROR("Can't get socket: %s", strerror(errno));
             goto out;
         }
-        port = ntohs(real_family == AF_INET6 ? sockname.sin6.sin6_port : sockname.sin.sin_port);
-        INFO("port is %d", port);
-    }
-    listener->listen_port = port;
+        rv = setsockopt(listener->io.fd, SOL_SOCKET, SO_REUSEADDR, &true_flag, sizeof true_flag);
+        if (rv < 0) {
+            ERROR("SO_REUSEADDR failed: %s", strerror(errno));
+            goto out;
+        }
 
+        rv = setsockopt(listener->io.fd, SOL_SOCKET, SO_REUSEPORT, &true_flag, sizeof true_flag);
+        if (rv < 0) {
+            ERROR("SO_REUSEPORT failed: %s", strerror(errno));
+            goto out;
+        }
+
+        // shift the DSCP value to the left by 2 bits to make the 8-bit field
+        int dscp = DSCP_CS5 << 2;
+        if (real_family == AF_INET6) {
+            // IPV6_TCLASS.
+            rv = setsockopt(listener->io.fd, IPPROTO_IPV6, IPV6_TCLASS, &dscp, sizeof(dscp));
+            if (rv < 0) {
+                ERROR("IPV6_TCLASS failed: %s", strerror(errno));
+                goto out;
+            }
+        } else {
+            // IP_TOS
+            rv = setsockopt(listener->io.fd, IPPROTO_IP, IP_TOS, &dscp, sizeof(dscp));
+            if (rv < 0) {
+                ERROR("IP_TOS failed: %s", strerror(errno));
+                goto out;
+            }
+        }
+        // skipping multicast support for now
+
+        if (family == AF_INET6) {
+            // Don't use a dual-stack socket.
+            rv = setsockopt(listener->io.fd, IPPROTO_IPV6, IPV6_V6ONLY, &true_flag, sizeof true_flag);
+            if (rv < 0) {
+                SEGMENTED_IPv6_ADDR_GEN_SRP(listener->address.sin6.sin6_addr.s6_addr, ipv6_addr_buf);
+                ERROR("Unable to set IPv6-only flag on UDP socket for " PRI_SEGMENTED_IPv6_ADDR_SRP,
+                      SEGMENTED_IPv6_ADDR_PARAM_SRP(listener->address.sin6.sin6_addr.s6_addr, ipv6_addr_buf));
+                goto out;
+            }
+            SEGMENTED_IPv6_ADDR_GEN_SRP(listener->address.sin6.sin6_addr.s6_addr, ipv6_addr_buf);
+            ERROR("Successfully set IPv6-only flag on UDP socket for " PRI_SEGMENTED_IPv6_ADDR_SRP,
+                  SEGMENTED_IPv6_ADDR_PARAM_SRP(listener->address.sin6.sin6_addr.s6_addr, ipv6_addr_buf));
+        }
+
+        sl = listener->address.sa.sa_len;
+        if (bind(listener->io.fd, &listener->address.sa, sl) < 0) {
+            if (family == AF_INET) {
+                IPv4_ADDR_GEN_SRP(&listener->address.sin.sin_addr.s_addr, ipv4_addr_buf);
+                ERROR("Can't bind to " PRI_IPv4_ADDR_SRP "#%d: %s",
+                      IPv4_ADDR_PARAM_SRP(&listener->address.sin.sin_addr.s_addr, ipv4_addr_buf), ntohs(port),
+                      strerror(errno));
+            } else {
+                SEGMENTED_IPv6_ADDR_GEN_SRP(&listener->address.sin6.sin6_addr.s6_addr, ipv6_addr_buf);
+                ERROR("Can't bind to " PRI_SEGMENTED_IPv6_ADDR_SRP "#%d: %s",
+                      SEGMENTED_IPv6_ADDR_PARAM_SRP(&listener->address.sin6.sin6_addr.s6_addr, ipv6_addr_buf), ntohs(port),
+                      strerror(errno));
+            }
+        out:
+            close(listener->io.fd);
+            listener->io.fd = -1;
+            RELEASE_HERE(listener, listener);
+            return NULL;
+        }
+    }
+
+    if (fcntl(listener->io.fd, F_SETFL, O_NONBLOCK) < 0) {
+        ERROR("%s: Can't set O_NONBLOCK: %s", listener->name, strerror(errno));
+        goto out;
+    }
+
+    // We may have bound to an unspecified port, so fetch the port we got. Or we may have got the port from
+    // launchd, in which case let's make sure we got the right port.
+    if (launchd_name != NULL || port == 0) {
+        sl = sizeof(sockname);
+        if (getsockname(listener->io.fd, (struct sockaddr *)&sockname, &sl) < 0) {
+            ERROR("getsockname: %s", strerror(errno));
+            goto out;
+        }
+        listener->listen_port = ntohs(real_family == AF_INET6 ? sockname.sin6.sin6_port : sockname.sin.sin_port);
+        if (launchd_name != NULL && listener->listen_port != port) {
+            ERROR("launchd port mismatch: %u %u", port, listener->listen_port);
+        }
+    } else {
+        listener->listen_port = port;
+    }
+    INFO("port is %d", listener->listen_port);
+
+    if (ifindex != 0) {
+        setsockopt(listener->io.fd, IPPROTO_IP, IP_BOUND_IF, &ifindex, sizeof(ifindex));
+        setsockopt(listener->io.fd, IPPROTO_IPV6, IPV6_BOUND_IF, &ifindex, sizeof(ifindex));
+    }
     rv = setsockopt(listener->io.fd, family == AF_INET ? IPPROTO_IP : IPPROTO_IPV6,
                     family == AF_INET ? IP_PKTINFO : IPV6_RECVPKTINFO, &true_flag, sizeof true_flag);
     if (rv < 0) {
@@ -1243,8 +1441,14 @@
     if (listener->ready != NULL) {
         RETAIN_HERE(listener, listener); // For the ready callback
         dispatch_async(ioloop_main_queue, ^{
-                if (listener->ready != NULL) {
-                    listener->ready(listener->context, port);
+                // It's possible that we might schedule the ready event but then before we return to the run loop
+                // the listener gets canceled, in which case we don't want to deliver the ready event.
+                if (listener->canceled) {
+                    INFO("ready but canceled");
+                } else {
+                    if (listener->ready != NULL) {
+                        listener->ready(listener->context, listener->listen_port);
+                    }
                 }
                 RELEASE_HERE(listener, listener);
             });
@@ -1254,11 +1458,11 @@
 #endif // UDP_LISTENER_USES_CONNECTION_GROUPS
 
 comm_t *
-ioloop_listener_create(bool stream, bool tls, uint16_t *avoid_ports, int num_avoid_ports,
+ioloop_listener_create(bool stream, bool tls, bool launchd, uint16_t *avoid_ports, int num_avoid_ports,
                        const addr_t *ip_address, const char *multicast, const char *name,
                        datagram_callback_t datagram_callback, connect_callback_t connected, cancel_callback_t cancel,
                        ready_callback_t ready, finalize_callback_t finalize, tls_config_callback_t tls_config,
-                       void *context)
+                       unsigned ifindex, void *context)
 {
     comm_t *listener;
     int family = (ip_address != NULL) ? ip_address->sa.sa_family : AF_UNSPEC;
@@ -1300,6 +1504,7 @@
         }
         return NULL;
     }
+    listener->serial = ++cur_connection_serial;
     if (avoid_ports != NULL) {
         listener->avoid_ports = malloc(num_avoid_ports * sizeof(uint16_t));
         if (listener->avoid_ports == NULL) {
@@ -1333,10 +1538,11 @@
     listener->ready = ready;
     listener->context = context;
     listener->tcp_stream = stream;
+    listener->is_listener = true;
 
 #if !UDP_LISTENER_USES_CONNECTION_GROUPS
     if (stream == FALSE) {
-        comm_t *ret = ioloop_udp_listener_setup(listener, ip_address, port);
+        comm_t *ret = ioloop_udp_listener_setup(listener, ip_address, port, launchd ? name : NULL, ifindex);
         if (ret == NULL) {
             return ret;
         }
@@ -1369,7 +1575,11 @@
                 snprintf(ip_address_str, sizeof(ip_address_str), "::");
             }
         } else {
-            inet_ntop(family, ip_address->sa.sa_data, ip_address_str, sizeof(ip_address_str));
+            if (family == AF_INET) {
+                inet_ntop(family, &ip_address->sin.sin_addr, ip_address_str, sizeof(ip_address_str));
+            } else {
+                inet_ntop(family, &ip_address->sin6.sin6_addr, ip_address_str, sizeof(ip_address_str));
+            }
         }
         endpoint = nw_endpoint_create_host(ip_address_str, portbuf);
         if (endpoint == NULL) {
@@ -1378,7 +1588,6 @@
             return NULL;
         }
     }
-
     if (stream) {
         nw_parameters_configure_protocol_block_t configure_tls_block = NW_PARAMETERS_DISABLE_PROTOCOL;
         if (tls && tls_config != NULL) {
@@ -1416,9 +1625,21 @@
     // Set SO_REUSEADDR.
     nw_parameters_set_reuse_local_address(listener->parameters, true);
 
+
     if (stream) {
         // Create the nw_listener_t.
-        listener->listener = nw_listener_create(listener->parameters);
+        listener->listener = NULL;
+#ifndef SRP_TEST_SERVER
+        if (launchd && name != NULL) {
+            listener->listener = nw_listener_create_with_launchd_key(listener->parameters, name);
+            if (listener->listener == NULL) {
+                ERROR("launchd listener create failed, trying to create it without relying on launchd.");
+            }
+        }
+#endif
+        if (listener->listener == NULL) {
+            listener->listener = nw_listener_create(listener->parameters);
+        }
         if (listener->listener == NULL) {
             ERROR("no memory for nw_listener object");
             RELEASE_HERE(listener, listener);
@@ -1426,17 +1647,22 @@
         }
         nw_listener_created++;
         nw_listener_set_new_connection_handler(listener->listener,
-                                               ^(nw_connection_t connection) { connection_callback(listener, connection); }
-                                               );
+                                               ^(nw_connection_t connection) {
+                                                   ioloop_listener_connection_callback(listener, connection);
+                                               });
 
-        RETAIN_HERE(listener, listener); // for the nw_listener_t
         nw_listener_set_state_changed_handler(listener->listener, ^(nw_listener_state_t state, nw_error_t error) {
             ioloop_listener_state_changed_handler(listener, state, error);
         });
+        RETAIN_HERE(listener, listener); // for the nw_listener_t state change handler callback
         nw_listener_set_queue(listener->listener, ioloop_main_queue);
         nw_listener_start(listener->listener);
 #if UDP_LISTENER_USES_CONNECTION_GROUPS
     } else {
+        if (launchd) {
+            FAULT("launchd not yet supported for connection groups");
+            return NULL;
+        }
         if (!ioloop_udp_listener_setup(listener)) {
             RELEASE_HERE(listener, listener);
             return NULL;
@@ -1470,6 +1696,8 @@
         ERROR("No memory for connection");
         return NULL;
     }
+    connection->serial = ++cur_connection_serial;
+
     // If we don't release this because of an error, this is the caller's reference to the comm_t.
     RETAIN_HERE(connection, comm);
     endpoint = nw_endpoint_create_host(addrbuf, portbuf);
@@ -1559,7 +1787,7 @@
     RETAIN_HERE(connection, comm); // The connection state changed handler has a reference to the connection.
     nw_connection_set_state_changed_handler(connection->connection,
                                             ^(nw_connection_state_t state, nw_error_t error)
-                                            { connection_state_changed(connection, state, error); });
+                                            { ioloop_connection_state_changed(connection, state, error); });
     nw_connection_set_queue(connection->connection, ioloop_main_queue);
     nw_connection_start(connection->connection);
     return connection;
@@ -1728,6 +1956,24 @@
     return subproc;
 }
 
+#ifdef SRP_TEST_SERVER
+void
+ioloop_dnssd_txn_cancel_srp(void *srp_server, dnssd_txn_t *txn)
+{
+    if (txn->sdref != NULL) {
+        INFO("txn %p serviceref %p", txn, txn->sdref);
+        if (srp_server != NULL) {
+            dns_service_ref_deallocate(srp_server, txn->sdref);
+        } else {
+            DNSServiceRefDeallocate(txn->sdref);
+        }
+        txn->sdref = NULL;
+    } else {
+        INFO("dead transaction.");
+    }
+}
+#endif
+
 void
 ioloop_dnssd_txn_cancel(dnssd_txn_t *txn)
 {
@@ -1858,20 +2104,37 @@
 }
 
 static void
-ioloop_read_cancel(void *context)
+ioloop_read_source_finalize(void *context)
 {
     io_t *io = context;
 
+    INFO("io %p fd %d, read source %p, write_source %p", io, io->fd, io->read_source, io->write_source);
+
+    // Release the reference count that dispatch was holding.
+    if (io->is_static) {
+        if (io->context_release != NULL) {
+            io->context_release(io->context);
+        }
+        FINALIZED(file_descriptor_finalized);
+    } else {
+        RELEASE_HERE(io, file_descriptor);
+    }
+}
+
+static void
+ioloop_read_source_cancel_callback(void *context)
+{
+    io_t *io = context;
+
+    INFO("io %p fd %d, read source %p, write_source %p", io, io->fd, io->read_source, io->write_source);
     if (io->read_source != NULL) {
         dispatch_release(io->read_source);
         io->read_source = NULL;
-        // Release the reference count that dispatch was holding.
-        if (io->is_static) {
-            if (io->context_release != NULL) {
-                io->context_release(io->context);
-            }
+        if (io->fd != -1) {
+            close(io->fd);
+            io->fd = -1;
         } else {
-            RELEASE_HERE(io, file_descriptor);
+            FAULT("io->fd has been set to -1 too early");
         }
     }
 }
@@ -1889,13 +2152,13 @@
 void
 ioloop_close(io_t *io)
 {
+    INFO("io %p fd %d, read source %p, write_source %p", io, io->fd, io->read_source, io->write_source);
     if (io->read_source != NULL) {
         dispatch_cancel(io->read_source);
     }
     if (io->write_source != NULL) {
         dispatch_cancel(io->write_source);
     }
-    io->fd = -1;
 }
 
 void
@@ -1910,10 +2173,12 @@
         return;
     }
     dispatch_source_set_event_handler_f(io->read_source, ioloop_read_event);
-    dispatch_source_set_cancel_handler_f(io->read_source, ioloop_read_cancel);
+    dispatch_source_set_cancel_handler_f(io->read_source, ioloop_read_source_cancel_callback);
+    dispatch_set_finalizer_f(io->read_source, ioloop_read_source_finalize);
     dispatch_set_context(io->read_source, io);
-    RETAIN_HERE(io, io); // Dispatch will hold a reference.
+    RETAIN_HERE(io, file_descriptor); // Dispatch will hold a reference.
     dispatch_resume(io->read_source);
+    INFO("io %p fd %d, read source %p, write_source %p", io, io->fd, io->read_source, io->write_source);
 }
 
 void
@@ -1925,30 +2190,19 @@
 }
 
 const struct sockaddr *
-connection_get_local_address(comm_t *connection)
+connection_get_local_address(message_t *message)
 {
-#if UDP_LISTENER_USES_CONNECTION_GROUPS
-    nw_endpoint_t local_endpoint = NULL;
-    nw_connection_group_t connection_group = connection->listener_state != NULL?
-                                             connection->listener_state->connection_group:
-                                             NULL;
-    const struct sockaddr *local_addr = NULL;
-
-    require_action_quiet(connection_group, exit, ERROR("connection group is NULL."));
-    require_action_quiet(connection->content_context, exit, ERROR("content_context is NULL."));
-    local_endpoint = nw_connection_group_copy_local_endpoint_for_message(connection_group, connection->content_context);
-    require_action(local_endpoint, exit, ERROR("no resources for local endpoint"));
-    local_addr = nw_endpoint_get_address(local_endpoint);
-    require_action(local_addr, exit, ERROR("fail to get local address"));
-
-exit:
-    if (local_endpoint != NULL) {
-        nw_release(local_endpoint);
+    if (message == NULL) {
+        ERROR("message is NULL.");
+        return NULL;
     }
-    return local_addr;
-#else
-    return &connection->address.sa;
-#endif // UDP_LISTENER_USES_CONNECTION_GROUPS
+    return &message->local.sa;
+}
+
+bool
+ioloop_is_device_apple_tv(void)
+{
+    return IsAppleTV();
 }
 
 // Local Variables:
diff --git a/ServiceRegistration/nat64.c b/ServiceRegistration/nat64.c
index 4fb5afc..8f0c126 100644
--- a/ServiceRegistration/nat64.c
+++ b/ServiceRegistration/nat64.c
@@ -102,11 +102,10 @@
 nat64_infra_prefix_monitor_cancel(nat64_infra_prefix_monitor_t *monitor)
 {
     if (monitor != NULL) {
-        nat64_prefix_t **ppref, *prefix;
-        ppref = &monitor->infra_nat64_prefixes;
-        while (*ppref != NULL) {
-            prefix = *ppref;
-            ppref = &prefix->next;
+        nat64_prefix_t *next;
+        for (nat64_prefix_t *prefix = monitor->infra_nat64_prefixes; prefix != NULL; prefix = next) {
+            next = prefix->next;
+            prefix->next = NULL;
             RELEASE_HERE(prefix, nat64_prefix);
         }
         monitor->infra_nat64_prefixes = NULL;
@@ -119,6 +118,7 @@
             RELEASE_HERE(monitor->nat64, nat64);
             monitor->nat64 = NULL;
         }
+        monitor->canceled = true;
     }
 }
 
@@ -142,6 +142,7 @@
         nat64_prefix_t *next;
         for (nat64_prefix_t *prefix = monitor->thread_nat64_prefixes; prefix != NULL; prefix = next) {
             next = prefix->next;
+            prefix->next = NULL;
             RELEASE_HERE(prefix, nat64_prefix);
         }
         monitor->thread_nat64_prefixes = NULL;
@@ -577,12 +578,15 @@
 } nat64_infra_prefix_monitor_state_t;
 
 static void
-nat64_query_infra_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex, DNSServiceErrorType errorCode, const char *fullname, uint16_t rrtype, uint16_t rrclass, uint16_t rdlen, const void *rdata, uint32_t ttl, void *context)
+nat64_query_infra_callback(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex,
+                           DNSServiceErrorType errorCode, const char *fullname, uint16_t rrtype, uint16_t rrclass,
+                           uint16_t rdlen, const void *rdata, uint32_t ttl, void *context)
 {
     (void)(sdRef);
     (void)(interfaceIndex);
 
     nat64_infra_prefix_monitor_t *state_machine = context;
+
     if (errorCode == kDNSServiceErr_NoError) {
         SEGMENTED_IPv6_ADDR_GEN_SRP(rdata, ipv6_rdata_buf);
         INFO("LLQ " PRI_S_SRP PRI_SEGMENTED_IPv6_ADDR_SRP
@@ -606,10 +610,18 @@
         }
         DNSServiceRefDeallocate(state_machine->sdRef);
         state_machine->sdRef = NULL;
-        RELEASE_HERE(state_machine, nat64_infra_prefix_monitor);
+
+        // We enter with a reference held on the state machine object. If there is no error, that means we got some kind
+        // of result, and so we don't release the reference because we can still get more results.  If, on the other hand,
+        // we get an error, we will restart the query after a delay. This means that the reference we were passed is
+        // still needed for the duration of the dispatch_after call. When that timer expires, if the state machine hasn't
+        // been canceled in the meantime, we restart the query.
         dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC),
                        dispatch_get_main_queue(), ^(void) {
-                           nat64_query_prefix_on_infra(state_machine);
+                           if (!state_machine->canceled) {
+                               nat64_query_prefix_on_infra(state_machine);
+                           }
+                           RELEASE_HERE(state_machine, nat64_infra_prefix_monitor);
                        });
     }
 }
@@ -621,13 +633,13 @@
 
     err = DNSServiceQueryRecord(&state_machine->sdRef, kDNSServiceFlagsLongLivedQuery, kDNSServiceInterfaceIndexAny, NAT64_PREFIX_LLQ_QUERY_DOMAIN, kDNSServiceType_AAAA, kDNSServiceClass_IN, nat64_query_infra_callback, state_machine);
     if (err != kDNSServiceErr_NoError) {
-        ERROR("DNSServiceQueryRecord failed for " PRI_S_SRP ": %d", NAT64_PREFIX_LLQ_QUERY_DOMAIN, err);
+        ERROR("DNSServiceQueryRecord failed for " PRI_S_SRP ": %d", NAT64_PREFIX_LLQ_QUERY_DOMAIN, (int)err);
         return false;
     }
-    RETAIN_HERE(state_machine, nat64_infra_prefix_monitor);
+    RETAIN_HERE(state_machine, nat64_infra_prefix_monitor); // For the callback.
     err = DNSServiceSetDispatchQueue(state_machine->sdRef, dispatch_get_main_queue());
     if (err != kDNSServiceErr_NoError) {
-        ERROR("DNSServiceSetDispatchQueue failed for " PRI_S_SRP ": %d", NAT64_PREFIX_LLQ_QUERY_DOMAIN, err);
+        ERROR("DNSServiceSetDispatchQueue failed for " PRI_S_SRP ": %d", NAT64_PREFIX_LLQ_QUERY_DOMAIN, (int)err);
         return false;
     }
     return true;
@@ -1117,7 +1129,9 @@
     NAT64_STATE_ANNOUNCE(state_machine, event);
     if (event == NULL) {
         return nat64_infra_prefix_publisher_state_invalid;
-    } else if (event->event_type == nat64_event_nat64_infra_prefix_publisher_infra_prefix_changed) {
+    } else if (event->event_type == nat64_event_nat64_infra_prefix_publisher_infra_prefix_changed ||
+               event->event_type == nat64_event_nat64_infra_prefix_publisher_shutdown)
+    {
         nat64_prefix_t *infra_prefix;
         for (infra_prefix = event->prefix; infra_prefix; infra_prefix = infra_prefix->next) {
             if (!in6prefix_compare(&infra_prefix->prefix, &state_machine->proposed_prefix->prefix, NAT64_PREFIX_SLASH_96_BYTES)) {
@@ -1145,7 +1159,9 @@
             INFO("no longer publishing infra prefix.");
             return nat64_infra_prefix_publisher_state_wait;
         }
-    } else if (event->event_type == nat64_event_nat64_infra_prefix_publisher_routable_omr_prefix_went_away) {
+    } else if (event->event_type == nat64_event_nat64_infra_prefix_publisher_routable_omr_prefix_went_away ||
+               event->event_type == nat64_event_nat64_infra_prefix_publisher_shutdown)
+    {
         // Routable OMR prefix is gone
         SEGMENTED_IPv6_ADDR_GEN_SRP(state_machine->proposed_prefix->prefix.s6_addr, nat64_prefix_buf);
         INFO("Routable OMR prefix is gone, unpublishing infra prefix " PRI_SEGMENTED_IPv6_ADDR_SRP,
@@ -1222,6 +1238,7 @@
     NAT64_EVENT_NAME_DECL(nat64_infra_prefix_publisher_infra_prefix_changed),
     NAT64_EVENT_NAME_DECL(nat64_infra_prefix_publisher_routable_omr_prefix_went_away),
     NAT64_EVENT_NAME_DECL(nat64_infra_prefix_publisher_routable_omr_prefix_showed_up),
+    NAT64_EVENT_NAME_DECL(nat64_infra_prefix_publisher_shutdown),
 };
 #define INFRA_PREFIX_PUBLISHER_NUM_EVENT_TYPES (sizeof(nat64_infra_prefix_publisher_event_configurations) / sizeof(nat64_infra_prefix_publisher_event_configuration_t))
 
@@ -1407,7 +1424,9 @@
                 }
             }
         }
-    } else if (event->event_type == nat64_event_nat64_br_prefix_publisher_ipv4_default_route_went_away) {
+    } else if (event->event_type == nat64_event_nat64_br_prefix_publisher_ipv4_default_route_went_away ||
+               event->event_type == nat64_event_nat64_br_prefix_publisher_shutdown)
+    {
         nat64_unpublish_br_prefix(state_machine);
         return nat64_br_prefix_publisher_state_wait_for_anything;
     } else if (event->event_type == nat64_event_nat64_br_prefix_publisher_infra_prefix_changed) {
@@ -1450,6 +1469,7 @@
     NAT64_EVENT_NAME_DECL(nat64_br_prefix_publisher_ipv4_default_route_went_away),
     NAT64_EVENT_NAME_DECL(nat64_br_prefix_publisher_thread_prefix_changed),
     NAT64_EVENT_NAME_DECL(nat64_br_prefix_publisher_infra_prefix_changed),
+    NAT64_EVENT_NAME_DECL(nat64_br_prefix_publisher_shutdown),
 };
 #define BR_PREFIX_PUBLISHER_NUM_EVENT_TYPES (sizeof(nat64_br_prefix_publisher_event_configurations) / sizeof(nat64_br_prefix_publisher_event_configuration_t))
 
@@ -1722,4 +1742,29 @@
         nat64_thread_prefix_monitor_event_deliver(state_machine, &out_event);
     }
 }
+
+void
+nat64_thread_shutdown(route_state_t *route_state)
+{
+    nat64_t *nat64 = route_state->nat64;
+    if (nat64->nat64_infra_prefix_publisher != NULL) {
+        nat64_infra_prefix_publisher_event_t infra_event;
+        nat64_infra_prefix_publisher_event_init(&infra_event, nat64_event_nat64_infra_prefix_publisher_shutdown);
+        nat64_infra_prefix_publisher_event_deliver(nat64->nat64_infra_prefix_publisher, &infra_event);
+    }
+    if (nat64->nat64_br_prefix_publisher != NULL) {
+        nat64_br_prefix_publisher_event_t br_event;
+        nat64_br_prefix_publisher_event_init(&br_event, nat64_event_nat64_br_prefix_publisher_shutdown);
+        nat64_br_prefix_publisher_event_deliver(nat64->nat64_br_prefix_publisher, &br_event);
+    }
+}
 #endif
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 120
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/nat64.h b/ServiceRegistration/nat64.h
index 70cde42..bd1e24e 100644
--- a/ServiceRegistration/nat64.h
+++ b/ServiceRegistration/nat64.h
@@ -128,6 +128,7 @@
     DNSServiceRef NULLABLE sdRef;    // LLQ sdRef
     nat64_prefix_t *NULLABLE infra_nat64_prefixes;
     nat64_t *NONNULL nat64;
+    bool canceled;
 } nat64_infra_prefix_monitor_t;
 
 
@@ -171,6 +172,7 @@
     nat64_event_nat64_infra_prefix_publisher_infra_prefix_changed,
     nat64_event_nat64_infra_prefix_publisher_routable_omr_prefix_went_away,
     nat64_event_nat64_infra_prefix_publisher_routable_omr_prefix_showed_up,
+    nat64_event_nat64_infra_prefix_publisher_shutdown,
 } nat64_infra_prefix_publisher_event_type_t;
 
 typedef struct nat64_infra_prefix_publisher_event {
@@ -206,6 +208,7 @@
     nat64_event_nat64_br_prefix_publisher_ipv4_default_route_went_away,
     nat64_event_nat64_br_prefix_publisher_thread_prefix_changed,
     nat64_event_nat64_br_prefix_publisher_infra_prefix_changed,
+    nat64_event_nat64_br_prefix_publisher_shutdown,
 } nat64_br_prefix_publisher_event_type_t;
 
 typedef struct nat64_br_prefix_publisher_event {
@@ -260,4 +263,14 @@
 void nat64_omr_route_update(nat64_t *NONNULL nat64, bool has_routable_omr_prefix);
 void nat64_stop(route_state_t *NONNULL route_state);
 void nat64_start(route_state_t *NONNULL route_state);
+void nat64_thread_shutdown(route_state_t *NONNULL route_state);
 #endif /* NAT64_H */
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 120
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/node-type-tracker.c b/ServiceRegistration/node-type-tracker.c
index b163264..ad0b9b4 100644
--- a/ServiceRegistration/node-type-tracker.c
+++ b/ServiceRegistration/node-type-tracker.c
@@ -64,7 +64,7 @@
     srp_server_t *server_state;
     cti_connection_t NULLABLE thread_context;
 	node_type_tracker_callback_t *callbacks;
-	time_t last_thread_network_node_type_change;
+	uint64_t last_thread_network_node_type_change;
 	thread_node_type_t current_node_type, previous_node_type;
 };
 
@@ -90,6 +90,7 @@
 		NODE_TYPE_TO_STRING(nest_lurker);
 		NODE_TYPE_TO_STRING(commissioner);
 		NODE_TYPE_TO_STRING(leader);
+		NODE_TYPE_TO_STRING(sleepy_router);
 	default:
 		return "<invalid>";
 	}
@@ -129,6 +130,9 @@
 	case kCTI_NetworkNodeType_SleepyEndDevice:
 		node_type = node_type_sleepy_end_device;
 		break;
+    case kCTI_NetworkNodeType_SynchronizedSleepyEndDevice:
+        node_type = node_type_synchronized_sleepy_end_device;
+        break;
 	case kCTI_NetworkNodeType_NestLurker:
 		node_type = node_type_nest_lurker;
 		break;
@@ -138,6 +142,9 @@
 	case kCTI_NetworkNodeType_Leader:
 		node_type = node_type_leader;
 		break;
+    case kCTI_NetworkNodeType_SleepyRouter:
+        node_type = node_type_sleepy_router;
+        break;
 	}
 
     tracker->last_thread_network_node_type_change = ioloop_timenow();
diff --git a/ServiceRegistration/node-type-tracker.h b/ServiceRegistration/node-type-tracker.h
index 829214f..3237710 100644
--- a/ServiceRegistration/node-type-tracker.h
+++ b/ServiceRegistration/node-type-tracker.h
@@ -27,12 +27,16 @@
     node_type_router,
     node_type_end_device,
     node_type_sleepy_end_device,
+    node_type_synchronized_sleepy_end_device,
     node_type_nest_lurker,
     node_type_commissioner,
     node_type_leader,
+    node_type_sleepy_router,
 } thread_node_type_t;
 
 RELEASE_RETAIN_DECLS(node_type_tracker);
+#define node_type_tracker_retain(watcher) node_type_tracker_retain_(watcher, __FILE__, __LINE__)
+#define node_type_tracker_release(watcher) node_type_tracker_release_(watcher, __FILE__, __LINE__)
 const char *NONNULL node_type_tracker_thread_node_type_to_string(thread_node_type_t node_type);
 void node_type_tracker_cancel(node_type_tracker_t *NONNULL publisher);
 node_type_tracker_t *NULLABLE node_type_tracker_create(srp_server_t *NONNULL route_state);
diff --git a/ServiceRegistration/object-types.h b/ServiceRegistration/object-types.h
index 96ea0cd..fe94521 100644
--- a/ServiceRegistration/object-types.h
+++ b/ServiceRegistration/object-types.h
@@ -39,6 +39,7 @@
 OBJECT_TYPE(dp_tracker)
 OBJECT_TYPE(file_descriptor)
 OBJECT_TYPE(interface)
+OBJECT_TYPE(ifpermit_list)
 OBJECT_TYPE(io)
 OBJECT_TYPE(listener)
 OBJECT_TYPE(message)
diff --git a/ServiceRegistration/omr-publisher.c b/ServiceRegistration/omr-publisher.c
index 2528117..773f981 100644
--- a/ServiceRegistration/omr-publisher.c
+++ b/ServiceRegistration/omr-publisher.c
@@ -1,6 +1,6 @@
 /* omr-publisher.c
  *
- * Copyright (c) 2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2023-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -45,6 +45,7 @@
 #include "route.h"
 #include "dnssd-proxy.h"
 
+
 #define STATE_MACHINE_IMPLEMENTATION 1
 typedef enum {
     omr_publisher_state_invalid,
@@ -61,6 +62,7 @@
 #include "thread-service.h"
 #include "omr-watcher.h"
 #include "omr-publisher.h"
+#include "route-tracker.h"
 
 typedef struct omr_publisher {
     int ref_count;
@@ -85,6 +87,7 @@
     omr_prefix_priority_t omr_priority, force_priority;
     bool ula_prefix_published, dhcp_prefix_published;
     bool dhcp_wanted;
+    bool dhcp_blocked;
     bool first_time;
     bool force_publication;
 } omr_publisher_t;
@@ -123,7 +126,6 @@
 static void omr_publisher_queue_prefix_update(omr_publisher_t *publisher, struct in6_addr *prefix_address,
                                               omr_prefix_priority_t priority, bool preferred,
                                               thread_service_publication_state_t initial_state);
-static void omr_publisher_unpublish_prefix(omr_publisher_t *publisher);
 
 static void
 omr_publisher_finalize(omr_publisher_t *publisher)
@@ -235,6 +237,7 @@
         interface_release(publisher->dhcp_interface);
         publisher->dhcp_interface = NULL;
     }
+    state_machine_cancel(&publisher->state_header);
 }
 
 omr_publisher_t *
@@ -354,7 +357,9 @@
 static void
 omr_publisher_initiate_dhcp(omr_publisher_t *publisher)
 {
-    publisher->dhcp_wanted = true;
+    if (!publisher->dhcp_blocked) {
+        publisher->dhcp_wanted = true;
+    }
     publisher->dhcp_client = (void *)-1;
 }
 
@@ -366,11 +371,10 @@
         if (publisher->dhcp_interface->inactive || publisher->dhcp_interface->ineligible) {
             // If we have a DHCPv6 client running, we need to discontinue it.
             if (publisher->dhcp_client != NULL) {
-                DHCPv6PDServiceRef NULLABLE dhcp_client = publisher->dhcp_client;
-                publisher->dhcp_client = NULL;
-                CFRelease(dhcp_client); // Release the publisher reference
-                omr_publisher_send_dhcp_event(publisher, NULL, 0, 0);
+                omr_publisher_dhcp_client_deactivate(publisher, (intptr_t)publisher->dhcp_client);
             }
+            interface_release(publisher->dhcp_interface);
+            publisher->dhcp_interface = NULL;
         }
     }
     if (publisher->dhcp_wanted && publisher->dhcp_client == NULL) {
@@ -383,11 +387,7 @@
 omr_publisher_discontinue_dhcp(omr_publisher_t *publisher)
 {
     INFO("discontinuing DHCP PD client");
-    if (publisher->dhcp_client != NULL) {
-        DHCPv6PDServiceRef NULLABLE dhcp_client = publisher->dhcp_client;
-        publisher->dhcp_client = NULL;
-        CFRelease(dhcp_client); // Release the publisher reference
-    }
+    omr_publisher_dhcp_client_deactivate(publisher, (intptr_t)publisher->dhcp_client);
     publisher->dhcp_wanted = false;
 }
 
@@ -459,6 +459,7 @@
 {
     bool ret = omr_publisher_prefix_present(publisher, omr_prefix_priority_high);
     if (ret) {
+        INFO("setting publisher->omr_priority to high");
         publisher->omr_priority = omr_prefix_priority_high;
     }
     return ret;
@@ -469,6 +470,7 @@
 {
     bool ret = omr_publisher_prefix_present(publisher, omr_prefix_priority_medium);
     if (ret) {
+        INFO("setting publisher->omr_priority to medium");
         publisher->omr_priority = omr_prefix_priority_medium;
     }
     return ret;
@@ -511,6 +513,33 @@
     return omr_publisher_prefix_wins(publisher, omr_prefix_priority_low);
 }
 
+bool
+omr_publisher_publishing_dhcp(omr_publisher_t *publisher)
+{
+    if (publisher->state_header.state == omr_publisher_state_publishing_dhcp)
+    {
+        return true;
+    }
+    return false;
+}
+
+bool
+omr_publisher_publishing_ula(omr_publisher_t *publisher)
+{
+    if (publisher->state_header.state == omr_publisher_state_publishing_ula)
+    {
+        return true;
+    }
+    return false;
+}
+
+bool
+omr_publisher_publishing_prefix(omr_publisher_t *publisher)
+{
+    return omr_publisher_publishing_dhcp(publisher) ||
+           omr_publisher_publishing_ula(publisher);
+}
+
 static void omr_publisher_queue_run(omr_publisher_t *publisher);
 
 static void
@@ -570,6 +599,7 @@
         cti_status_t status = cti_remove_prefix(publisher->route_state->srp_server, publisher,
                                                 omr_publisher_prefix_update_callback, NULL, &prefix->prefix,
                                                 prefix->prefix_length);
+        SEGMENTED_IPv6_ADDR_GEN_SRP(prefix, prefix_buf);
         INFO("removing prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d",
              SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length);
         if (status != kCTIStatus_NoError) {
@@ -580,6 +610,7 @@
             RETAIN_HERE(publisher, omr_publisher); // for the callback
         }
     } else if (prefix->publication_state == want_add) {
+        SEGMENTED_IPv6_ADDR_GEN_SRP(prefix, prefix_buf);
         INFO("adding prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d",
              SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length);
         cti_status_t status = cti_add_prefix(publisher->route_state->srp_server, publisher,
@@ -595,6 +626,7 @@
             RETAIN_HERE(publisher, omr_publisher); // for the callback
         }
     } else {
+        SEGMENTED_IPv6_ADDR_GEN_SRP(prefix, prefix_buf);
         INFO("prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d is in unexpected state " PUB_S_SRP " on the publication queue",
              SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length,
              thread_service_publication_state_name_get(prefix->publication_state));
@@ -632,7 +664,8 @@
     *ppref = prefix;
     // The prefix on the queue is retained by relying on the create/copy rule. When adding a prefix we also retain the
     // prefix as publisher->published_prefix, so that retain is always explicit and this retain is always implicit.
-    omr_prefix_retain(*ppref);
+    // omr_prefix_retain(*ppref);
+
     // If there is anything in the queue, the queue holds a reference to the publisher, so that it will continue to
     // run until it's complete.
     if (old_queue == NULL && publisher->publication_queue != NULL) {
@@ -645,11 +678,16 @@
 omr_publisher_publish_prefix(omr_publisher_t *publisher,
                              struct in6_addr *prefix_address, omr_prefix_priority_t priority, bool preferred)
 {
+    SEGMENTED_IPv6_ADDR_GEN_SRP(prefix_address, prefix_buf);
+    INFO("publishing prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/64",
+         SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix_address, prefix_buf));
     omr_publisher_queue_prefix_update(publisher, prefix_address, priority, preferred, want_add);
+    INFO("setting publisher->omr_priority to %d, " PUB_S_SRP "preferred", omr_prefix_priority_to_int(priority),
+         preferred ? "" : "not ");
     publisher->omr_priority = priority;
 }
 
-static void
+void
 omr_publisher_unpublish_prefix(omr_publisher_t *publisher)
 {
     omr_prefix_t *prefix;
@@ -660,6 +698,7 @@
         ERROR("request to unpublished prefix that's not present");
         return;
     }
+    SEGMENTED_IPv6_ADDR_GEN_SRP(prefix, prefix_buf);
     INFO("unpublishing prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d",
          SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length);
     omr_publisher_queue_prefix_update(publisher, &prefix->prefix, prefix->priority, false, want_delete);
@@ -713,7 +752,9 @@
 bool
 omr_publisher_have_routable_prefix(omr_publisher_t *publisher)
 {
-    if (publisher->omr_priority == omr_prefix_priority_medium || publisher->omr_priority == omr_prefix_priority_high) {
+    if ((publisher->published_prefix != NULL && omr_watcher_prefix_is_non_ula_prefix(publisher->published_prefix)) ||
+        (publisher->omr_watcher != NULL && omr_watcher_non_ula_prefix_present(publisher->omr_watcher)))
+    {
         INFO("we have a routable prefix");
         return true;
     }
@@ -972,6 +1013,36 @@
     }
 }
 
+void
+omr_publisher_check_prefix(omr_publisher_t *publisher, struct in6_addr *prefix, int UNUSED len)
+{
+    if (publisher == NULL) {
+        return;
+    }
+    if (publisher->published_prefix == NULL) {
+        return;
+    }
+    // Make sure that this prefix, which we are seeing advetised on infrastructure, is not published as the OMR prefix.
+    if (!in6prefix_compare(&publisher->published_prefix->prefix, prefix, 8)) {
+        if (!in6prefix_compare(&publisher->ula_prefix, prefix, 8)) {
+            SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->s6_addr, prefix_buf);
+            FAULT("ULA prefix is being advertised on infrastructure: " PRI_SEGMENTED_IPv6_ADDR_SRP,
+                  SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->s6_addr, prefix_buf));
+        } else {
+            // If we get here it means that our DHCP prefix is bogus and we can't use it. So we're going to block DHCP, and treat this as
+            // a DHCP prefix loss.
+            SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->s6_addr, prefix_buf);
+            ERROR("DHCP prefix is being advertised on infrastructure: " PRI_SEGMENTED_IPv6_ADDR_SRP,
+                  SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->s6_addr, prefix_buf));
+
+            publisher->dhcp_wanted = false;
+            publisher->dhcp_blocked = true;
+            omr_publisher_dhcp_client_deactivate(publisher, (intptr_t)publisher->dhcp_client);
+            omr_publisher_send_dhcp_event(publisher, NULL, 0, 0);
+        }
+    }
+}
+
 // Local Variables:
 // mode: C
 // tab-width: 4
diff --git a/ServiceRegistration/omr-publisher.h b/ServiceRegistration/omr-publisher.h
index f567100..530d0d1 100644
--- a/ServiceRegistration/omr-publisher.h
+++ b/ServiceRegistration/omr-publisher.h
@@ -30,6 +30,8 @@
 #define OMR_PUBLISHER_MIN_START              3000 // three seconds (minimum, for new router coming to existing network)
 
 RELEASE_RETAIN_DECLS(omr_publisher);
+#define omr_publisher_retain(publisher) omr_publisher_retain_(publisher, __FILE__, __LINE__)
+#define omr_publisher_release(publisher) omr_publisher_release_(publisher, __FILE__, __LINE__)
 void omr_publisher_cancel(omr_publisher_t *NONNULL publisher);
 omr_publisher_t *NULLABLE omr_publisher_create(route_state_t *NONNULL route_state, const char *NONNULL name);
 void omr_publisher_set_omr_watcher(omr_publisher_t *NONNULL omr_publisher, omr_watcher_t *NONNULL omr_watcher);
@@ -39,6 +41,9 @@
 omr_prefix_t *NULLABLE omr_publisher_published_prefix_get(omr_publisher_t *NONNULL publisher);
 void omr_publisher_force_publication(omr_publisher_t *NONNULL publisher, omr_prefix_priority_t priority);
 void omr_publisher_interface_configuration_changed(omr_publisher_t *NONNULL publisher);
+bool omr_publisher_publishing_dhcp(omr_publisher_t *NONNULL publisher);
+bool omr_publisher_publishing_ula(omr_publisher_t *NONNULL publisher);
+bool omr_publisher_publishing_prefix(omr_publisher_t *NONNULL publisher);
 
 // The OMR publisher knows whether the prefix being published can be used for routing, even if it's not publishing it itself.
 // If there is a medium- or high-priority prefix published by some other router, that prefix can be assumed to be routable.
@@ -47,6 +52,12 @@
 // Of course it still works for routing between Thread and the adjacent infrastructure link--what it can't be used for is
 // routing across a multi-link infrastructure network or to the internet.
 bool omr_publisher_have_routable_prefix(omr_publisher_t *NONNULL publisher);
+
+// Check that the prefix that we saw in an RA is not also the one we are publishing on Thread. This can happen with broken DHCP
+// PD servers, and we have seen it in some home routers.
+void omr_publisher_check_prefix(omr_publisher_t *NULLABLE publisher, struct in6_addr *NONNULL prefix, int len);
+
+void omr_publisher_unpublish_prefix(omr_publisher_t *NONNULL publisher);
 #endif // __OMR_PUBLISHER_H__
 
 // Local Variables:
diff --git a/ServiceRegistration/omr-watcher.c b/ServiceRegistration/omr-watcher.c
index e06ac95..92db5be 100644
--- a/ServiceRegistration/omr-watcher.c
+++ b/ServiceRegistration/omr-watcher.c
@@ -1,6 +1,6 @@
 /* omr-watcher.c
  *
- * Copyright (c) 2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2023-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -70,13 +70,13 @@
 #include "srp.h"
 #include "dns-msg.h"
 #include "ioloop.h"
-#include "adv-ctl-server.h"
 #include "srp-crypto.h"
 
 #include "cti-services.h"
 #include "srp-gw.h"
 #include "srp-proxy.h"
 #include "srp-mdns-proxy.h"
+#include "adv-ctl-server.h"
 #include "dnssd-proxy.h"
 #include "srp-proxy.h"
 #include "route.h"
@@ -107,6 +107,7 @@
     bool purge_pending;
     bool first_time;
     bool prefix_recheck_pending;
+    bool awaiting_unpublication;
 };
 
 static void
@@ -220,6 +221,19 @@
 static void
 omr_watcher_finalize(omr_watcher_t *omw)
 {
+    omr_prefix_t *next;
+
+    if (omw->prefix_recheck_wakeup != NULL) {
+        ioloop_cancel_wake_event(omw->prefix_recheck_wakeup);
+        ioloop_wakeup_release(omw->prefix_recheck_wakeup);
+        omw->prefix_recheck_wakeup = NULL;
+    }
+
+    for (omr_prefix_t *prefix = omw->prefixes; prefix != NULL; prefix = next) {
+        next = prefix->next;
+        RELEASE_HERE(prefix, omr_prefix);
+    }
+
     // The omr_watcher_t can have a route_connection and a prefix_connection, but each of these will retain
     // a reference to the omr_watcher, so we can't get here while these connections are still alive. Hence,
     // we do not need to free them here.
@@ -250,6 +264,7 @@
             pcb = &((*pcb)->next);
         }
     }
+    RELEASE_HERE(omw, omr_watcher);
 }
 
 static void
@@ -272,6 +287,7 @@
     size_t i;
     omr_prefix_t **ppref = &omw->prefixes, *prefix = NULL, **new = NULL;
     bool something_changed = false;
+    bool user_prefix_seen = false;
 
     INFO("status: %d  prefixes: %p  count: %d", status, prefixes, prefixes == NULL ? -1 : (int)prefixes->num);
 
@@ -356,6 +372,7 @@
             if (cti_prefix->ncp) {
                 prefix->ncp = true;
             } else {
+                user_prefix_seen = true;
                 prefix->user = true;
             }
             if (cti_prefix->stable) {
@@ -381,6 +398,7 @@
     }
     INFO("omw->prefixes = %p", omw->prefixes);
     for (prefix = omw->prefixes; prefix != NULL; prefix = prefix->next) {
+        SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf);
         INFO("prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d is currently in the list " PUB_S_SRP PUB_S_SRP PUB_S_SRP,
              SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length,
              prefix->user ? " (user)" : "", prefix->ncp ? " (ncp)": "", prefix->stable ? " (stable)" : "");
@@ -389,6 +407,10 @@
         omr_watcher_send_prefix_event(omw, omr_watcher_event_prefix_update_finished, omw->prefixes, NULL);
         omw->first_time = false;
     }
+    if (!user_prefix_seen && omw->route_state->srp_server->awaiting_prefix_removal) {
+        omw->route_state->srp_server->awaiting_prefix_removal = false;
+        adv_ctl_thread_shutdown_status_check(omw->route_state->srp_server);
+    }
 out:
     // Discontinue events (currently we'll only get one callback: this just dereferences the object so it can be freed.)
     INFO("prefix_connections_pending = %d", omw->prefix_connections_pending);
@@ -515,7 +537,8 @@
         watcher->prefix_recheck_pending = true;
     }
 
-    int rv = cti_get_onmesh_prefix_list(watcher->route_state->srp_server, &watcher->prefix_connection, watcher, omr_watcher_prefix_list_callback, NULL);
+    int rv = cti_get_onmesh_prefix_list(watcher->route_state->srp_server, &watcher->prefix_connection,
+                                        watcher, omr_watcher_prefix_list_callback, NULL);
     if (rv != kCTIStatus_NoError) {
         ERROR("can't get onmesh prefix list: %d", rv);
         return;
@@ -619,10 +642,10 @@
                            struct in6_addr *ignore_prefix, int ignore_prefix_length)
 {
     static struct in6_addr in6addr_zero;
-    SEGMENTED_IPv6_ADDR_GEN_SRP(ignore_prefix->prefix.s6_addr, prefix_buf);
+    SEGMENTED_IPv6_ADDR_GEN_SRP(ignore_prefix, ignore_buf);
     if (in6addr_compare(ignore_prefix, &in6addr_zero)) {
         INFO("prefix to ignore: " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d",
-             SEGMENTED_IPv6_ADDR_PARAM_SRP(ignore_prefix->s6_addr, prefix_buf), ignore_prefix_length);
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(ignore_prefix->s6_addr, ignore_buf), ignore_prefix_length);
     }
     for (omr_prefix_t *prefix = watcher->prefixes; prefix != NULL; prefix = prefix->next) {
         if (prefix->prefix_length == ignore_prefix_length && !in6addr_compare(&prefix->prefix, ignore_prefix)) {
@@ -646,9 +669,24 @@
 }
 
 bool
+omr_watcher_non_ula_prefix_present(omr_watcher_t *watcher)
+{
+    for (omr_prefix_t *prefix = watcher->prefixes; prefix != NULL; prefix = prefix->next) {
+        if (omr_watcher_prefix_is_non_ula_prefix(prefix)) {
+            SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf);
+            INFO("matched prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d",
+                 SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf), prefix->prefix_length);
+            return true;
+        }
+    }
+    INFO("returning false");
+    return false;
+}
+
+bool
 omr_watcher_prefix_exists(omr_watcher_t *watcher, const struct in6_addr *address, int prefix_length)
 {
-    SEGMENTED_IPv6_ADDR_GEN_SRP(address->prefix.s6_addr, target_buf);
+    SEGMENTED_IPv6_ADDR_GEN_SRP(address, target_buf);
     INFO("address: " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d",
          SEGMENTED_IPv6_ADDR_PARAM_SRP(address->s6_addr, target_buf), prefix_length);
     for (omr_prefix_t *prefix = watcher->prefixes; prefix != NULL; prefix = prefix->next) {
diff --git a/ServiceRegistration/omr-watcher.h b/ServiceRegistration/omr-watcher.h
index f038e63..d5ff299 100644
--- a/ServiceRegistration/omr-watcher.h
+++ b/ServiceRegistration/omr-watcher.h
@@ -1,6 +1,6 @@
 /* omr-watcher.h
  *
- * Copyright (c) 2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2023-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -60,7 +60,11 @@
 
 // Release/retain functions for omr_watcher_t:
 RELEASE_RETAIN_DECLS(omr_watcher);
+#define omr_watcher_retain(watcher) omr_watcher_retain_(watcher, __FILE__, __LINE__)
+#define omr_watcher_release(watcher) omr_watcher_release_(watcher, __FILE__, __LINE__)
 RELEASE_RETAIN_DECLS(omr_prefix);
+#define omr_prefix_retain(prefix) omr_prefix_retain_(prefix, __FILE__, __LINE__)
+#define omr_prefix_release(prefix) omr_prefix_release_(prefix, __FILE__, __LINE__)
 
 // omr_prefix_create
 //
@@ -199,6 +203,16 @@
 bool
 omr_watcher_prefix_remove(omr_watcher_t *NONNULL watcher, const void *NONNULL data, int prefix_length);
 
+// omw_watcher_non_ula_prefix_present
+//
+// Returns true if there is an on-mesh prefix in the thread network data that is not a ULA prefix (implicitly a global prefix)
+//
+
+bool omr_watcher_non_ula_prefix_present(omr_watcher_t *NONNULL watcher);
+
+
+#define omr_watcher_prefix_is_non_ula_prefix(omr_prefix) ((((uint8_t *)&(omr_prefix)->prefix)[0] & 0xfc) != 0xfc)
+
 #endif // _OMR_WATCHER_H__
 
 // Local Variables:
diff --git a/ServiceRegistration/posix.c b/ServiceRegistration/posix.c
index 95ff769..9edcb66 100644
--- a/ServiceRegistration/posix.c
+++ b/ServiceRegistration/posix.c
@@ -36,6 +36,9 @@
 #include "srp.h"
 #include "dns-msg.h"
 #include "ioloop.h"
+#ifdef SRP_TEST_SERVER
+#include "test-api.h"
+#endif
 
 #undef OBJECT_TYPE
 #define OBJECT_TYPE(x) int x##_created, x##_finalized, old_##x##_created, old_##x##_finalized;
@@ -71,6 +74,12 @@
             INFO(PUB_S_SRP, outbuf);
         }
     }
+    int num_fds = get_num_fds();
+    if (num_fds < 0) {
+        FAULT("out of file descriptors!!");
+        abort();
+    }
+    INFO("%d file descriptors in use", num_fds);
 }
 
 interface_address_state_t *interface_addresses;
@@ -87,21 +96,70 @@
 }
 
 bool
-ioloop_map_interface_addresses(const char *ifname, void *context, interface_callback_t callback)
+ioloop_map_interface_addresses(srp_server_t *server_state, const char *ifname, void *context,
+                               interface_callback_t callback)
 {
-    return ioloop_map_interface_addresses_here(&interface_addresses, ifname, context, callback);
+    return ioloop_map_interface_addresses_here(server_state, &interface_addresses, ifname, context, callback);
+}
+
+static bool
+ioloop_same_address(struct sockaddr *a, addr_t *b, struct sockaddr *ma, addr_t *sk)
+{
+    // If the family is different, addresses are definitely not the same
+    if (a->sa_family != b->sa.sa_family) {
+        return false;
+    }
+
+    // For IPv4 addresses, both the address and the netmask must match
+    if (a->sa_family == AF_INET && b->sa.sa_family == AF_INET && ma->sa_family == AF_INET) {
+        struct sockaddr_in *a4 = (struct sockaddr_in *)a;
+        struct sockaddr_in *ma4 = (struct sockaddr_in *)ma;
+
+        if (a4->sin_addr.s_addr == b->sin.sin_addr.s_addr && ma4->sin_addr.s_addr == sk->sin.sin_addr.s_addr) {
+            return true;
+        }
+    }
+
+    // For IPv6 adddresses, same deal
+    else if (a->sa_family == AF_INET6 && b->sa.sa_family == AF_INET6 && ma->sa_family == AF_INET6) {
+        struct sockaddr_in6 *a6 = (struct sockaddr_in6 *)a;
+        struct sockaddr_in6 *ma6 = (struct sockaddr_in6 *)ma;
+
+        if (!memcmp(&a6->sin6_addr, &b->sin6.sin6_addr, sizeof b->sin6.sin6_addr) &&
+            !memcmp(&ma6->sin6_addr, &sk->sin6.sin6_addr, sizeof sk->sin6.sin6_addr))
+        {
+            return true;
+        }
+    }
+#ifndef LINUX
+    // For AF_LINK addresses, there is no netmask, and we are assuming a 6-byte ethernet address.
+    else if (a->sa_family == AF_LINK && b->sa.sa_family == AF_LINK) {
+        struct sockaddr_dl *sdl = (struct sockaddr_dl *)a;
+        if (sdl->sdl_alen == 6 && !memcmp(LLADDR(sdl), b->ether_addr.addr, 6) && b->ether_addr.index == sdl->sdl_index)
+        {
+            return true;
+        }
+    }
+#endif
+
+    return false; // Unknown address family, don't know how to compare, don't really care.
 }
 
 bool
-ioloop_map_interface_addresses_here_(interface_address_state_t **here, const char *ifname, void *context,
-                                     interface_callback_t callback, const char *file, int line)
+ioloop_map_interface_addresses_here_(srp_server_t *server_state, interface_address_state_t **here, const char *ifname,
+                                     void *context, interface_callback_t callback, const char *file, int line)
 {
     struct ifaddrs *ifaddrs, *ifp;
     interface_address_state_t *kept_ifaddrs = NULL, **ki_end = &kept_ifaddrs;
     interface_address_state_t *new_ifaddrs = NULL, **ni_end = &new_ifaddrs;
     interface_address_state_t **ip, *nif;
 
-    if (getifaddrs(&ifaddrs) < 0) {
+#ifdef SRP_TEST_SERVER
+    int ret = srp_test_getifaddrs(server_state, &ifaddrs, context);
+#else
+    int ret = getifaddrs(&ifaddrs);
+#endif
+    if (ret < 0) {
         ERROR("getifaddrs failed: " PUB_S_SRP, strerror(errno));
         return false;
     }
@@ -174,21 +232,7 @@
                 interface_address_state_t *ia = *ip;
                 // Same interface and address?
                 if (!remove && !strcmp(ia->name, ifp->ifa_name) &&
-                    ifp->ifa_addr->sa_family == ia->addr.sa.sa_family &&
-                    (
-#ifndef LINUX
-                        ifp->ifa_addr->sa_family == AF_LINK ||
-#endif
-                     (((ifp->ifa_addr->sa_family == AF_INET &&
-                        ((struct sockaddr_in *)ifp->ifa_addr)->sin_addr.s_addr == ia->addr.sin.sin_addr.s_addr) ||
-                       (ifp->ifa_addr->sa_family == AF_INET6 &&
-                        !memcmp(&((struct sockaddr_in6 *)ifp->ifa_addr)->sin6_addr,
-                                &ia->addr.sin6.sin6_addr, sizeof ia->addr.sin6.sin6_addr))) &&
-                      ((ifp->ifa_netmask->sa_family == AF_INET &&
-                        ((struct sockaddr_in *)ifp->ifa_netmask)->sin_addr.s_addr == ia->mask.sin.sin_addr.s_addr) ||
-                       (ifp->ifa_netmask->sa_family == AF_INET6 &&
-                        !memcmp(&((struct sockaddr_in6 *)ifp->ifa_netmask)->sin6_addr,
-                                &ia->mask.sin6.sin6_addr, sizeof ia->mask.sin6.sin6_addr))))))
+                    ioloop_same_address(ifp->ifa_addr, &ia->addr, ifp->ifa_netmask, &ia->mask))
                 {
                     *ip = ia->next;
                     *ki_end = ia;
@@ -240,16 +284,20 @@
                         if (sdl->sdl_alen == 6) {
                             nif->addr.ether_addr.len = 6;
                             memcpy(nif->addr.ether_addr.addr, LLADDR(sdl), 6);
+                            nif->addr.ether_addr.index = sdl->sdl_index;
+                            nif->addr.ether_addr.family = AF_LINK;
                         } else {
-                            nif->addr.ether_addr.len = 0;
+                            free(nif);
+                            nif = NULL;
                         }
-                        nif->addr.ether_addr.index = sdl->sdl_index;
-                        nif->addr.ether_addr.family = AF_LINK;
+
 #endif // LINUX
                     }
-                    nif->flags = ifp->ifa_flags;
-                    *ni_end = nif;
-                    ni_end = &nif->next;
+                    if (nif != NULL) {
+                        nif->flags = ifp->ifa_flags;
+                        *ni_end = nif;
+                        ni_end = &nif->next;
+                    }
                 }
             }
         }
@@ -262,13 +310,34 @@
     for (ip = &new_ifaddrs; *ip; ) {
         if ((*ip)->addr.sa.sa_family == AF_LINK) {
             bool drop = true;
-            for (nif = new_ifaddrs; nif; nif = nif->next) {
-                if (nif != *ip && !strcmp(nif->name, (*ip)->name)) {
-                    drop = false;
-                    break;
+            // We need to iterate across both new_ifaddrs and kept_ifaddrs to find all of the addresses on an
+            // interface. Only if there are no IP addresses on either list for the interface for which we have
+            // the AF_LINK address do we drop the AF_LINK address.
+            for (int q = 0; q < 2; q++) {
+                interface_address_state_t *list = q ? kept_ifaddrs : new_ifaddrs;
+                for (nif = list; nif; nif = nif->next) {
+                    if (nif != *ip && nif->addr.sa.sa_family != AF_LINK && !strcmp(nif->name, (*ip)->name)) {
+#define TOO_MUCH_INFO
+#ifdef TOO_MUCH_INFO
+                        char buf[INET6_ADDRSTRLEN];
+                        if (nif->addr.sa.sa_family == AF_INET6) {
+                            inet_ntop(AF_INET6, &nif->addr.sin6.sin6_addr, buf, sizeof(buf));
+                        } else if (nif->addr.sa.sa_family == AF_INET) {
+                            inet_ntop(AF_INET, &nif->addr.sin6.sin6_addr, buf, sizeof(buf));
+                        }
+                        INFO("new link-layer address not dropped because " PRI_S_SRP " - ifname: " PUB_S_SRP ", addr: "
+                             PRI_MAC_ADDR_SRP, buf, (*ip)->name, MAC_ADDR_PARAM_SRP((*ip)->addr.ether_addr.addr));
+#endif // TOO_MUCH_INFO
+                        drop = false;
+                        break;
+                    }
                 }
             }
             if (drop) {
+#ifdef TOO_MUCH_INFO
+                INFO("new link-layer interface address dropped - ifname: " PUB_S_SRP
+                     ", addr: " PRI_MAC_ADDR_SRP, (*ip)->name, MAC_ADDR_PARAM_SRP((*ip)->addr.ether_addr.addr));
+#endif
                 nif = *ip;
                 *ip = nif->next;
                 free(nif);
@@ -305,11 +374,19 @@
             abort();
         }
         for (; nif; nif = nif->next) {
-            snprintf(infop, lim, " %p (", nif);
+            snprintf(infop, lim, "\n%p %s (", nif, nif->name);
             len = (int)strlen(infop);
             lim -= len;
             infop += len;
-            inet_ntop(AF_INET6, &nif->addr.sin6.sin6_addr, infop, lim);
+            if (nif->addr.sa.sa_family == AF_INET6) {
+                inet_ntop(AF_INET6, &nif->addr.sin6.sin6_addr, infop, lim);
+            } else if (nif->addr.sa.sa_family == AF_INET) {
+                inet_ntop(AF_INET, &nif->addr.sin.sin_addr, infop, lim);
+            } else if (nif->addr.sa.sa_family == AF_LINK) {
+                snprintf(infop, lim, "%02x:%02x:%02x:%02x:%02x:%02x",
+                         nif->addr.ether_addr.addr[0], nif->addr.ether_addr.addr[1], nif->addr.ether_addr.addr[2],
+                         nif->addr.ether_addr.addr[3], nif->addr.ether_addr.addr[4], nif->addr.ether_addr.addr[5]);
+            }
             len = (int)strlen(infop);
             lim -= len;
             infop += len;
@@ -328,7 +405,7 @@
         nif = *ip;
         *ip = nif->next;
         if (callback != NULL) {
-            callback(context, nif->name, &nif->addr, &nif->mask, nif->flags, interface_address_deleted);
+            callback(server_state, context, nif->name, &nif->addr, &nif->mask, nif->flags, interface_address_deleted);
         }
         free(nif);
     }
@@ -336,14 +413,14 @@
     // Report added interface addresses...
     for (nif = new_ifaddrs; nif; nif = nif->next) {
         if (callback != NULL) {
-            callback(context, nif->name, &nif->addr, &nif->mask, nif->flags, interface_address_added);
+            callback(server_state, context, nif->name, &nif->addr, &nif->mask, nif->flags, interface_address_added);
         }
     }
 
     // Report unchanged interface addresses...
     for (nif = kept_ifaddrs; nif; nif = nif->next) {
         if (callback != NULL) {
-            callback(context, nif->name, &nif->addr, &nif->mask, nif->flags, interface_address_unchanged);
+            callback(server_state, context, nif->name, &nif->addr, &nif->mask, nif->flags, interface_address_unchanged);
         }
     }
 
@@ -352,7 +429,11 @@
     for (ip = here; *ip; ip = &(*ip)->next)
         ;
     *ip = new_ifaddrs;
+#ifdef SRP_TEST_SERVER
+    srp_test_freeifaddrs(server_state, ifaddrs, context);
+#else
     freeifaddrs(ifaddrs);
+#endif
     return true;
 }
 
@@ -439,7 +520,8 @@
 }
 
 // Return continuous time, if provided by O.S., otherwise unadjusted time.
-time_t srp_time(void)
+time_t
+srp_time(void)
 {
 #ifdef CLOCK_BOOTTIME
     // CLOCK_BOOTTIME is a Linux-specific constant that indicates a monotonic time that includes time asleep
@@ -459,14 +541,67 @@
     return tm.tv_sec;
 }
 
-#ifdef DEBUG_FD_LEAKS
+// Return continuous time, if provided by O.S., otherwise unadjusted time, in seconds, with six digits of
+// fractional accuracy.
+double
+srp_fractional_time(void)
+{
+#ifdef CLOCK_BOOTTIME
+    // CLOCK_BOOTTIME is a Linux-specific constant that indicates a monotonic time that includes time asleep
+    const int clockid = CLOCK_BOOTTIME;
+#elif defined(CLOCK_MONOTONIC_RAW)
+    // On MacOS, CLOCK_MONOTONIC_RAW is a monotonic time that includes time asleep and is not adjusted.
+    // According to the man page, CLOCK_MONOTONIC on MacOS violates the POSIX spec in that it can be adjusted.
+    const int clockid = CLOCK_MONOTONIC_RAW;
+#else
+    // On other Posix systems, CLOCK_MONOTONIC should be the right thing, at least according to the POSIX spec.
+    const int clockid = CLOCK_MONOTONIC;
+#endif
+    struct timespec tm;
+    clock_gettime(clockid, &tm);
+
+    return (double)tm.tv_sec + (double)tm.tv_nsec / 1.0e9;
+}
+
+// Return continuous time in microseconds, if provided by O.S., otherwise unadjusted time.
+int64_t
+srp_utime(void)
+{
+#ifdef CLOCK_BOOTTIME
+    // CLOCK_BOOTTIME is a Linux-specific constant that indicates a monotonic time that includes time asleep
+    const int clockid = CLOCK_BOOTTIME;
+#elif defined(CLOCK_MONOTONIC_RAW)
+    // On MacOS, CLOCK_MONOTONIC_RAW is a monotonic time that includes time asleep and is not adjusted.
+    // According to the man page, CLOCK_MONOTONIC on MacOS violates the POSIX spec in that it can be adjusted.
+    const int clockid = CLOCK_MONOTONIC_RAW;
+#else
+    // On other Posix systems, CLOCK_MONOTONIC should be the right thing, at least according to the POSIX spec.
+    const int clockid = CLOCK_MONOTONIC;
+#endif
+    struct timespec tm;
+    clock_gettime(clockid, &tm);
+
+    // We are only accurate to the second.
+    uint64_t utime = (int64_t)tm.tv_sec * 1000 * 1000 + tm.tv_nsec / 1000;
+    return utime;
+}
+
 int
 get_num_fds(void)
 {
-    DIR *dirfd = opendir("/dev/fd");
     int num = 0;
+    DIR *dirfd = opendir("/dev/fd");
     if (dirfd == NULL) {
-        return -1;
+        if (errno == EMFILE) {
+            FAULT("per-process open file limit reached.");
+            return -1;
+        } else if (errno == ENFILE) {
+            FAULT("per-system open file limit reached.");
+            return -1;
+        } else {
+            ERROR("errno %d " PUB_S_SRP, errno, strerror(errno));
+            return 0;
+        }
     }
     while (readdir(dirfd) != NULL) {
         num++;
@@ -474,7 +609,6 @@
     closedir(dirfd);
     return num;
 }
-#endif // DEBUG_VERBOSE
 
 #ifdef MALLOC_DEBUG_LOGGING
 #undef malloc
diff --git a/ServiceRegistration/probe-srp.c b/ServiceRegistration/probe-srp.c
index b9cdb39..c7466f9 100644
--- a/ServiceRegistration/probe-srp.c
+++ b/ServiceRegistration/probe-srp.c
@@ -65,6 +65,7 @@
     thread_service_t *service;
     void *context;
     void (*callback)(thread_service_t *service, void *context, bool succeeded);
+    void (*context_release)(void *context);
     route_state_t *route_state;
     dns_wire_t question;
     int num_retransmissions, retransmission_delay;
@@ -77,9 +78,45 @@
     if (probe_state->wakeup != NULL) {
         ioloop_wakeup_release(probe_state->wakeup);
     }
+    if (probe_state->service != NULL) {
+        thread_service_release(probe_state->service);
+        probe_state->service = NULL;
+    }
+    if (probe_state->connection != NULL) {
+        ioloop_comm_release(probe_state->connection);
+        probe_state->connection = NULL;
+    }
+    if (probe_state->context_release) {
+        probe_state->context_release(probe_state->context);
+    }
     free(probe_state);
 }
 
+void
+probe_srp_service_probe_cancel(thread_service_t *service)
+{
+    probe_state_t *probe_state = service->probe_state;
+    probe_state->service = NULL;
+    service->probe_state = NULL;
+
+    if (probe_state->context_release != NULL) {
+        probe_state->context_release(probe_state->context);
+    }
+    probe_state->context = NULL;
+
+    thread_service_release(service); // The probe state's reference to the service
+    if (probe_state->wakeup != NULL) {
+        ioloop_cancel_wake_event(probe_state->wakeup);
+    }
+
+    if (probe_state->connection != NULL) {
+        ioloop_comm_cancel(probe_state->connection); // Cancel the connection (should result in the state being released)
+        ioloop_comm_release(probe_state->connection);
+        probe_state->connection = NULL;
+    }
+    RELEASE_HERE(probe_state, probe_state); // The thread_service_t's reference to the probe state
+}
+
 static void
 probe_srp_done(void *context, bool succeeded)
 {
@@ -96,7 +133,13 @@
         port = 53;
     } else {
         address = &service->u.unicast.address;
-        port = (service->u.unicast.port[0] << 8) | service->u.unicast.port[1];
+        // If the anycast service is present, we can use port 53, which we need to prefer because pre-2024 Apple BRs
+        // will not answer DNS queries on the SRP service port.
+        if (service->u.unicast.anycast_also_present) {
+            port = 53;
+        } else {
+            port = (service->u.unicast.port[0] << 8) | service->u.unicast.port[1];
+        }
     }
     SEGMENTED_IPv6_ADDR_GEN_SRP(address->s6_addr, addr_buf);
     if (!succeeded) {
@@ -104,14 +147,12 @@
              SEGMENTED_IPv6_ADDR_PARAM_SRP(address->s6_addr, addr_buf), port);
         service->checking = false;
         service->ignore = true;  // Don't consider this service when deciding what to advertise
-        service->remove = false; // Keep the service around so we don't keep probing it.
         service->responding = false;
     } else {
         INFO("service " PRI_SEGMENTED_IPv6_ADDR_SRP " responded on port %d",
              SEGMENTED_IPv6_ADDR_PARAM_SRP(address->s6_addr, addr_buf), port);
         service->checking = false;
         service->ignore = false;
-        service->remove = false;
         service->checked = true;
     }
     service->responding = true;
@@ -120,9 +161,12 @@
         probe_state->callback(probe_state->service, probe_state->context, succeeded);
         probe_state->callback = NULL;
     }
+    if (probe_state->context_release != NULL) {
+        probe_state->context_release(probe_state->context);
+    }
     probe_state->context = NULL;
-    ioloop_comm_cancel(probe_state->connection); // Cancel the connection (should result in the state being released)
-    thread_service_release(service);
+
+    thread_service_release(service); // The probe state's reference to the service
     if (probe_state->wakeup != NULL) {
         ioloop_cancel_wake_event(probe_state->wakeup);
     }
@@ -130,18 +174,24 @@
 }
 
 static void
-probe_srp_datagram(comm_t *UNUSED connection, message_t *message, void *context)
+probe_srp_datagram(comm_t *connection, message_t *message, void *context)
 {
 #ifdef PROBE_SRP_TCP
     (void)message;
     // We should never get a datagram
     ERROR("got a datagram on %p", context);
 #else
+    int rcode = dns_rcode_get(&message->wire);
     probe_state_t *probe_state = context;
-    SEGMENTED_IPv6_ADDR_GEN_SRP(&address->sin6.sin6_addr, addr_buf);
-    INFO("datagram from " PRI_SEGMENTED_IPv6_ADDR_SRP " on port %d xid %x (question xid %x)",
-         SEGMENTED_IPv6_ADDR_PARAM_SRP(&probe_state->connection->address.sin6.sin6_addr, addr_buf),
-         ntohs(probe_state->connection->address.sin6.sin6_port), message->wire.id, probe_state->question.id);
+    if (connection->connection != NULL) {
+        INFO("datagram from " PRI_S_SRP " on port %d xid %x (question xid %x) rcode %d", connection->name,
+             ntohs(probe_state->connection->address.sin6.sin6_port), message->wire.id, probe_state->question.id, rcode);
+    } else {
+        SEGMENTED_IPv6_ADDR_GEN_SRP(&probe_state->connection->address.sin6.sin6_addr, addr_buf);
+        INFO("datagram from " PRI_SEGMENTED_IPv6_ADDR_SRP " on port %d xid %x (question xid %x) rcode %d",
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(&probe_state->connection->address.sin6.sin6_addr, addr_buf),
+             ntohs(probe_state->connection->address.sin6.sin6_port), message->wire.id, probe_state->question.id, rcode);
+    }
     if (message->wire.id != probe_state->question.id) {
         return; // not a response to the question we asked
     }
@@ -151,15 +201,23 @@
         return;
     }
     dns_message_free(dns_message);
-    probe_srp_done(context, true);
+    // If we get a servfail, treat it like a dropped packet, since that might just mean that the remote end is
+    // temporarily busy.
+    if (rcode == dns_rcode_servfail) {
+        return;
+    }
+    probe_srp_done(context, rcode == dns_rcode_noerror);
+    ioloop_comm_cancel(probe_state->connection); // Cancel the connection (should result in the state being released)
+    if (probe_state->wakeup != NULL) {
+        ioloop_cancel_wake_event(probe_state->wakeup);
+    }
 #endif
 }
 
 static void
-probe_srp_connection_finalize(void *context)
+probe_srp_probe_state_context_release(void *context)
 {
     probe_state_t *probe_state = context;
-    probe_state->connection = NULL;
     RELEASE_HERE(probe_state, probe_state);
 }
 
@@ -177,6 +235,7 @@
     // object.
     if (probe_state->connection != NULL) {
         ioloop_comm_release(probe_state->connection);
+        probe_state->connection = NULL;
     }
 }
 
@@ -255,11 +314,11 @@
     dns_opcode_set(&probe_state->question, dns_opcode_query);
     probe_state->question.qdcount = htons(1); // Just ask one question
 
-    // Query localhost.
-    dns_full_name_to_wire(NULL, &towire, "localhost");
-    dns_u16_to_wire(&towire, dns_rrtype_a);
+    // Query SOA for default.service.arpa--if this fails, we can't use this server.
+    dns_full_name_to_wire(NULL, &towire, "default.service.arpa");
+    dns_u16_to_wire(&towire, dns_rrtype_soa);
     dns_u16_to_wire(&towire, dns_qclass_in);
-    probe_state->question_length = towire.p - (uint8_t *)&probe_state->question;
+    probe_state->question_length = (uint16_t)(towire.p - (uint8_t *)&probe_state->question);
 
     // We're not in a hurry; the goal is to probe.
     probe_state->retransmission_delay = 1000; // milliseconds
@@ -271,8 +330,9 @@
 }
 
 static probe_state_t *
-probe_state_create(addr_t *address, thread_service_t *service, void *context,
-                   void (*callback)(thread_service_t *service, void *context, bool succeeded))
+probe_srp_create(addr_t *address, thread_service_t *service, void *context,
+                   void (*callback)(thread_service_t *service, void *context, bool succeeded),
+                   void (*context_release)(void *context))
 {
     probe_state_t *ret = NULL, *probe_state = calloc(1, sizeof(*probe_state));
     if (probe_state == NULL) {
@@ -283,7 +343,7 @@
     // tls   stream stable  opportunistic
     probe_state->connection = ioloop_connection_create(address, false, false, false, false,
                                                        probe_srp_datagram, probe_srp_connected, probe_srp_disconnected,
-                                                       probe_srp_connection_finalize, probe_state);
+                                                       probe_srp_probe_state_context_release, probe_state);
     if (probe_state->connection == NULL) {
         INFO("failed to create connection");
         goto out;
@@ -312,6 +372,9 @@
     if (ret == NULL && callback != NULL) {
         dispatch_async(dispatch_get_main_queue(), ^{
             callback(service, context, true); // We claim success here because this should never fail; if it does, it's our problem.
+            if (context_release != NULL) {
+                context_release(context);
+            }
         });
     }
     return ret;
@@ -320,20 +383,21 @@
 // If we've been asked to probe a service, go through the list.
 static probe_state_t *
 probe_srp_anycast_service(thread_service_t *service, void *context,
-                          void (*callback)(thread_service_t *service, void *context, bool succeeded))
+                          void (*callback)(thread_service_t *service, void *context, bool succeeded),
+                          void (*context_release)(void *context))
 {
     addr_t address;
     memset(&address, 0, sizeof(address));
     memcpy(&address.sin6.sin6_addr, &service->u.anycast.address, sizeof(service->u.anycast.address));
     address.sin6.sin6_port = htons(53);
     address.sa.sa_family = AF_INET6;
-    return probe_state_create(&address, service, context, callback);
+    return probe_srp_create(&address, service, context, callback, context_release);
 }
 
 static probe_state_t *
 probe_srp_unicast_service(thread_service_t *service, void *context,
-                          void (*callback)(thread_service_t *service, void *context, bool succeeded))
-{
+                          void (*callback)(thread_service_t *service, void *context, bool succeeded),
+                          void (*context_release)(void *context)){
     if (service->checking || service->user) {
         return NULL;
     }
@@ -342,29 +406,33 @@
     address.sa.sa_family = AF_INET6;
     memcpy(&address.sin6.sin6_addr, &service->u.unicast.address, sizeof(address.sin6.sin6_addr));
     memcpy(&address.sin6.sin6_port, service->u.unicast.port, sizeof(address.sin6.sin6_port));
-    return probe_state_create(&address, service, context, callback);
+    return probe_srp_create(&address, service, context, callback, context_release);
 }
 
 void
 probe_srp_service(thread_service_t *service, void *context,
-                  void (*callback)(thread_service_t *service, void *context, bool succeeded))
+                  void (*callback)(thread_service_t *service, void *context, bool succeeded),
+                  void (*context_release)(void *context))
 {
     probe_state_t *probe_state;
     if (service->service_type == unicast_service) {
-        probe_state = probe_srp_unicast_service(service, context, callback);
+        probe_state = probe_srp_unicast_service(service, context, callback, context_release);
     } else if (service->service_type == anycast_service){
-        probe_state = probe_srp_anycast_service(service, context, callback);
+        probe_state = probe_srp_anycast_service(service, context, callback, context_release);
     } else {
         FAULT("bogus service type in probe_srp_service: %d", service->service_type);
         if (callback != NULL) {
             dispatch_async(dispatch_get_main_queue(), ^{
                     callback(service, context, false); // False because this isn't a valid service
+                    if (context_release) {
+                        context_release(context);
+                    }
                 });
         }
         return;
     }
 
-    // probe_state_create returns this retained, but we don't store the pointer.
+    // probe_srp_create returns this retained, but we don't store the pointer.
     RELEASE_HERE(probe_state, probe_state);
 }
 
diff --git a/ServiceRegistration/probe-srp.h b/ServiceRegistration/probe-srp.h
index e2dc3be..cfe93d5 100644
--- a/ServiceRegistration/probe-srp.h
+++ b/ServiceRegistration/probe-srp.h
@@ -21,7 +21,10 @@
 #define __PROBE_SRP_H__ 1
 typedef struct probe_state probe_state_t;
 void probe_srp_service(thread_service_t *NONNULL service, void *NULLABLE context,
-                       void (*NONNULL callback)(thread_service_t *NONNULL service, void *NULLABLE context, bool succeeded));
+                       void (*NONNULL callback)(thread_service_t *NONNULL service,
+                                                void *NULLABLE context, bool succeeded),
+                       void (*NULLABLE context_release_callback)(void *NONNULL context));
+void probe_srp_service_probe_cancel(thread_service_t *NONNULL service);
 #endif // __PROBE_SRP_H__
 
 // Local Variables:
diff --git a/ServiceRegistration/route-tracker.c b/ServiceRegistration/route-tracker.c
index 8500f15..7c13184 100644
--- a/ServiceRegistration/route-tracker.c
+++ b/ServiceRegistration/route-tracker.c
@@ -48,19 +48,28 @@
 #include "route.h"
 #include "nat64.h"
 #include "nat64-macos.h"
-
-#define STATE_MACHINE_IMPLEMENTATION 1
-typedef enum {
-    route_tracker_state_invalid,
-} state_machine_state_t;
-#define state_machine_state_invalid route_tracker_state_invalid
-
+#include "adv-ctl-server.h"
 #include "state-machine.h"
 #include "thread-service.h"
 #include "omr-watcher.h"
 #include "omr-publisher.h"
 #include "route-tracker.h"
 
+#ifdef BUILD_TEST_ENTRY_POINTS
+#undef cti_remove_route
+#undef cti_add_route
+#define cti_remove_route cti_remove_route_test
+#define cti_add_route cti_add_route_test
+
+static int cti_add_route_test(srp_server_t *NULLABLE UNUSED server, void *NULLABLE context, cti_reply_t NONNULL callback,
+                              run_context_t NULLABLE UNUSED client_queue, struct in6_addr *NONNULL prefix,
+                              int UNUSED prefix_length, int UNUSED priority, int UNUSED domain_id, bool UNUSED stable,
+                              bool UNUSED nat64);
+static int cti_remove_route_test(srp_server_t *NULLABLE UNUSED server, void *NULLABLE context, cti_reply_t NONNULL callback,
+                                 run_context_t NULLABLE UNUSED client_queue, struct in6_addr *NONNULL prefix,
+                                 int UNUSED prefix_length, int UNUSED priority);
+#endif
+
 typedef struct prefix_tracker prefix_tracker_t;
 struct prefix_tracker {
     int ref_count;
@@ -85,6 +94,8 @@
     prefix_tracker_t *update_queue;
     interface_t *infrastructure;
     bool canceled;
+    bool user_route_seen;
+    bool blocked;
 #ifdef BUILD_TEST_ENTRY_POINTS
     uint32_t current_mask, add_mask, remove_mask, intended_mask;
     cti_reply_t callback;
@@ -236,6 +247,10 @@
 route_tracker_start(route_tracker_t *tracker)
 {
     INFO("starting tracker " PUB_S_SRP, tracker->name);
+    // Immediately check to see if we can advertise a prefix.
+#ifndef BUILD_TEST_ENTRY_POINTS
+    route_tracker_interface_configuration_changed(tracker);
+#endif
     return;
 }
 
@@ -457,10 +472,48 @@
     }
 }
 
+bool
+route_tracker_check_for_gua_prefixes_on_infrastructure(route_tracker_t *tracker)
+{
+    bool present = false;
+    interface_t *interface = tracker->infrastructure;
+    if (tracker == NULL || interface == NULL) {
+        goto out;
+    }
+    for (icmp_message_t *router = interface->routers; router != NULL; router = router->next) {
+        for (int i = 0; i < router->num_options; i++) {
+            icmp_option_t *option = &router->options[i];
+            if (option->type == icmp_option_prefix_information) {
+                prefix_information_t *prefix = &option->option.prefix_information;
+                if (omr_watcher_prefix_is_non_ula_prefix(prefix)) {
+                    present = true;
+                    goto done;
+                }
+            } else if (option->type == icmp_option_route_information) {
+                route_information_t *rio = &option->option.route_information;
+                if (omr_watcher_prefix_is_non_ula_prefix(rio)) {
+                    present = true;
+                    goto done;
+                }
+            }
+        }
+    }
+done:
+    INFO("interface " PUB_S_SRP ": checked for GUAs on infrastructure: " PUB_S_SRP "present",
+         interface->name, present ? "" : "not ");
+out:
+    return present;
+}
+
+
 #ifndef BUILD_TEST_ENTRY_POINTS
 void
 route_tracker_route_state_changed(route_tracker_t *tracker, interface_t *interface)
 {
+    if (tracker->blocked) {
+        INFO("tracker is blocked");
+        return;
+    }
     if (tracker->route_state == NULL) {
         ERROR("tracker has no route_state");
         return;
@@ -476,30 +529,30 @@
     if (interface != tracker->infrastructure) {
         return;
     }
-    INFO("interface: " PUB_S_SRP, interface != NULL ? interface->name : "(no interface)");
+    bool need_default_route = have_routable_omr_prefix;
+    // We need a default route if there is a routable omr prefix, but also if there are GUA prefixes on the adjacent
+    // infrastructure link or reachable via the adjacent infrastructure link's router(s).
+    if (interface != NULL && !need_default_route) {
+        need_default_route = route_tracker_check_for_gua_prefixes_on_infrastructure(tracker);
+    }
+    INFO("interface: " PUB_S_SRP PUB_S_SRP " need default route, " PUB_S_SRP " routable OMR prefix",
+         interface != NULL ? interface->name : "(no interface)", need_default_route ? "" : " don't",
+         have_routable_omr_prefix ? "have" : "don't have");
 
     route_tracker_reset_counts(tracker);
 
     // If we have no interface, then all we really care about is that any routes we're publishing should be
     // removed.
     if (interface != NULL) {
-#if SRP_FEATURE_PUBLISH_SPECIFIC_ROUTES
-        for (icmp_message_t *router = interface->routers; router != NULL; router = router->next) {
-            if (have_routable_omr_prefix && router->router_lifetime != 0) {
-#endif
-                static struct in6_addr default_prefix;
-                route_tracker_track_prefix(tracker, &default_prefix, 0, 1800, 1800, true);
-#if SRP_FEATURE_PUBLISH_SPECIFIC_ROUTES
-            }
-            route_tracker_count_prefixes(tracker, router, have_routable_omr_prefix);
+        static struct in6_addr prefix;
+        int width = 0;
+        if (need_default_route) {
+            ((uint8_t *)&prefix)[0] = 0;
+        } else {
+            ((uint8_t *)&prefix)[0] = 0xfc;
+            width = 7;
         }
-#endif
-#if SRP_FEATURE_PUBLISH_SPECIFIC_ROUTES
-        // Track our own prefix
-        if (interface->on_link_prefix_configured) {
-            route_tracker_track_prefix(tracker, &interface->ipv6_prefix, 64, 1800, 1800, true);
-        }
-#endif
+        route_tracker_track_prefix(tracker, &prefix, width, 1800, 1800, true);
     }
 #if SRP_FEATURE_NAT64
     nat64_omr_route_update(route_state->nat64, have_routable_omr_prefix);
@@ -511,6 +564,10 @@
 route_tracker_interface_configuration_changed(route_tracker_t *tracker)
 {
     interface_t *preferred = NULL;
+    if (tracker->blocked) {
+        INFO("tracker is blocked");
+        return;
+    }
     if (tracker->route_state == NULL) {
         ERROR("tracker has no route_state");
         return;
@@ -561,12 +618,38 @@
 void
 route_tracker_monitor_mesh_routes(route_tracker_t *tracker, cti_route_vec_t *routes)
 {
+    tracker->user_route_seen = false;
     for (size_t i = 0; i < routes->num; i++) {
         cti_route_t *route = routes->routes[i];
         if (route && route->origin == offmesh_route_origin_user) {
             route_tracker_track_prefix(tracker, &route->prefix, route->prefix_length, 100, 100, false);
+            tracker->user_route_seen = true;
         }
     }
+    if (!tracker->user_route_seen && tracker->route_state->srp_server->awaiting_route_removal) {
+        tracker->route_state->srp_server->awaiting_route_removal = false;
+        adv_ctl_thread_shutdown_status_check(tracker->route_state->srp_server);
+    }
+}
+
+bool
+route_tracker_local_routes_seen(route_tracker_t *tracker)
+{
+    if (tracker != NULL) {
+        return tracker->user_route_seen;
+    }
+    return false;
+}
+
+void
+route_tracker_shutdown(route_state_t *route_state)
+{
+    if (route_state == NULL || route_state->route_tracker == NULL) {
+        return;
+    }
+    route_tracker_reset_counts(route_state->route_tracker);
+    route_tracker_publish_changes(route_state->route_tracker);
+    route_state->route_tracker->blocked = true;
 }
 #else // !defined(BUILD_TEST_ENTRY_POINTS)
 
@@ -604,18 +687,19 @@
 }
 
 int
-cti_add_route_(srp_server_t *NULLABLE UNUSED server, void *NULLABLE context, cti_reply_t NONNULL callback, run_context_t NULLABLE UNUSED client_queue,
-               struct in6_addr *NONNULL prefix, int UNUSED prefix_length, int UNUSED priority, int UNUSED domain_id, bool UNUSED stable,
-               bool UNUSED nat64, const char *NONNULL UNUSED file, int UNUSED line)
+cti_add_route_test(srp_server_t *NULLABLE UNUSED server, void *NULLABLE context, cti_reply_t NONNULL callback,
+                   run_context_t NULLABLE UNUSED client_queue, struct in6_addr *NONNULL prefix,
+                   int UNUSED prefix_length, int UNUSED priority, int UNUSED domain_id, bool UNUSED stable,
+                   bool UNUSED nat64)
 {
     route_tracker_test_route_update(context, prefix, callback, false);
     return 0;
 }
 
 int
-cti_remove_route_(srp_server_t *NULLABLE UNUSED server, void *NULLABLE context, cti_reply_t NONNULL callback, run_context_t NULLABLE UNUSED client_queue,
-                  struct in6_addr *NONNULL prefix, int UNUSED prefix_length, int UNUSED priority,
-                  const char *NONNULL UNUSED file, int UNUSED line)
+cti_remove_route_test(srp_server_t *NULLABLE UNUSED server, void *NULLABLE context, cti_reply_t NONNULL callback,
+                      run_context_t NULLABLE UNUSED client_queue, struct in6_addr *NONNULL prefix,
+                      int UNUSED prefix_length, int UNUSED priority)
 {
     route_tracker_test_route_update(context, prefix, callback, true);
     return 0;
diff --git a/ServiceRegistration/route-tracker.h b/ServiceRegistration/route-tracker.h
index 6543ef8..0358501 100644
--- a/ServiceRegistration/route-tracker.h
+++ b/ServiceRegistration/route-tracker.h
@@ -25,6 +25,8 @@
 
 #ifndef BUILD_TEST_ENTRY_POINTS
 RELEASE_RETAIN_DECLS(route_tracker);
+#define route_tracker_retain(watcher) route_tracker_retain_(watcher, __FILE__, __LINE__)
+#define route_tracker_release(watcher) route_tracker_release_(watcher, __FILE__, __LINE__)
 #else
 typedef struct route_state route_state_t;
 typedef struct interface interface_t;
@@ -38,10 +40,13 @@
 void route_tracker_set_reconnect_callback(route_tracker_t *NONNULL route_tracker,
                                           void (*NULLABLE reconnect_callback)(void *NULLABLE context));
 void route_tracker_start(route_tracker_t *NONNULL tracker);
+void route_tracker_shutdown(route_state_t *NULLABLE route_state);
+bool route_tracker_check_for_gua_prefixes_on_infrastructure(route_tracker_t *NULLABLE tracker);
 #ifndef BUILD_TEST_ENTRY_POINTS
 void route_tracker_route_state_changed(route_tracker_t *NONNULL tracker, interface_t *NULLABLE interface);
 void route_tracker_interface_configuration_changed(route_tracker_t *NONNULL tracker);
 void route_tracker_monitor_mesh_routes(route_tracker_t *NONNULL tracker, cti_route_vec_t *NONNULL routes);
+bool route_tracker_local_routes_seen(route_tracker_t *NULLABLE tracker);
 #else //  BUILD_TEST_ENTRY_POINTS
 void route_tracker_test_start(int iterations);
 #endif //  BUILD_TEST_ENTRY_POINTS
diff --git a/ServiceRegistration/route.c b/ServiceRegistration/route.c
index d1347bf..f37f3f1 100644
--- a/ServiceRegistration/route.c
+++ b/ServiceRegistration/route.c
@@ -1,6 +1,6 @@
 /* route.c
  *
- * Copyright (c) 2019-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2019-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -74,12 +74,13 @@
 #include "srp.h"
 #include "dns-msg.h"
 #include "ioloop.h"
-#include "adv-ctl-server.h"
 #include "srp-crypto.h"
 #include "srp-gw.h"
 #include "srp-mdns-proxy.h"
+#include "adv-ctl-server.h"
 #include "srp-replication.h"
 
+
 # define THREAD_DATA_DIR "/var/lib/openthread"
 # define THREAD_ULA_FILE THREAD_DATA_DIR "/thread-mesh-ula"
 
@@ -104,17 +105,13 @@
 #include "omr-watcher.h"
 #include "omr-publisher.h"
 #include "route-tracker.h"
+#include "icmp.h"
+
 
 #ifdef LINUX
 #define CONFIGURE_STATIC_INTERFACE_ADDRESSES_WITH_IFCONFIG 1
 #endif
 
-struct icmp_listener {
-    io_t *io_state;
-    int sock;
-    uint32_t unsolicited_interval;
-};
-
 #ifdef LINUX
 struct in6_addr in6addr_linklocal_allnodes = {{{ 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
                                                  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 }}};
@@ -122,7 +119,6 @@
                                                    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 }}};
 #endif
 
-icmp_listener_t icmp_listener;
 route_state_t *route_states;
 
 #define CONFIGURE_STATIC_INTERFACE_ADDRESSES 1
@@ -131,10 +127,6 @@
 interface_t *NULLABLE interface_create_(route_state_t *NONNULL route_state, const char *NONNULL name, int ifindex,
                                         const char *NONNULL file, int line);
 
-static void router_advertisement_send(interface_t *NONNULL interface, const struct in6_addr *destination);
-static void neighbor_solicit_send(interface_t *interface, struct in6_addr *destination);
-static void icmp_send(uint8_t *NONNULL message, size_t length,
-                      interface_t *NONNULL interface, const struct in6_addr *NONNULL destination);
 static void interface_beacon_schedule(interface_t *NONNULL interface, unsigned when);
 static void interface_prefix_configure(struct in6_addr prefix, interface_t *NONNULL interface);
 static void interface_prefix_evaluate(interface_t *interface);
@@ -278,255 +270,6 @@
 #endif // RA_TESTER
 
 static void
-icmp_message_free(icmp_message_t *message)
-{
-    if (message->options != NULL) {
-        free(message->options);
-    }
-    if (message->wakeup != NULL) {
-        ioloop_cancel_wake_event(message->wakeup);
-        ioloop_wakeup_release(message->wakeup);
-    }
-    free(message);
-}
-
-static void
-icmp_message_dump(icmp_message_t *message,
-                  const struct in6_addr * const source_address, const struct in6_addr * const destination_address)
-{
-    link_layer_address_t *lladdr;
-    prefix_information_t *prefix_info;
-    route_information_t *route_info;
-    uint8_t *flags;
-    int i;
-    char retransmission_timer_buf[11]; // Maximum size of a uint32_t printed as decimal.
-    char *retransmission_timer = "infinite";
-
-    if (message->retransmission_timer != ND6_INFINITE_LIFETIME) {
-        snprintf(retransmission_timer_buf, sizeof(retransmission_timer_buf), "%" PRIu32, message->retransmission_timer);
-        retransmission_timer = retransmission_timer_buf;
-    }
-
-    SEGMENTED_IPv6_ADDR_GEN_SRP(source_address->s6_addr, src_addr_buf);
-    SEGMENTED_IPv6_ADDR_GEN_SRP(destination_address->s6_addr, dst_addr_buf);
-    if (message->type == icmp_type_router_advertisement) {
-        INFO("router advertisement from " PRI_SEGMENTED_IPv6_ADDR_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP
-             " hop_limit %d on " PUB_S_SRP ": checksum = %x "
-             "cur_hop_limit = %d flags = %x router_lifetime = %d reachable_time = %" PRIu32
-             " retransmission_timer = " PUB_S_SRP,
-             SEGMENTED_IPv6_ADDR_PARAM_SRP(source_address->s6_addr, src_addr_buf),
-             SEGMENTED_IPv6_ADDR_PARAM_SRP(destination_address->s6_addr, dst_addr_buf),
-             message->hop_limit, message->interface->name, message->checksum, message->cur_hop_limit, message->flags,
-             message->router_lifetime, message->reachable_time, retransmission_timer);
-    } else if (message->type == icmp_type_router_solicitation) {
-        INFO("router solicitation from " PRI_SEGMENTED_IPv6_ADDR_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP
-             " hop_limit %d on " PUB_S_SRP ": code = %d checksum = %x",
-             SEGMENTED_IPv6_ADDR_PARAM_SRP(source_address->s6_addr, src_addr_buf),
-             SEGMENTED_IPv6_ADDR_PARAM_SRP(destination_address->s6_addr, dst_addr_buf),
-             message->hop_limit, message->interface->name,
-             message->code, message->checksum);
-    } else {
-        INFO("icmp message from " PRI_SEGMENTED_IPv6_ADDR_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP " hop_limit %d on "
-             PUB_S_SRP ": type = %d code = %d checksum = %x",
-             SEGMENTED_IPv6_ADDR_PARAM_SRP(source_address->s6_addr, src_addr_buf),
-             SEGMENTED_IPv6_ADDR_PARAM_SRP(destination_address->s6_addr, dst_addr_buf),
-             message->hop_limit, message->interface->name, message->type,
-             message->code, message->checksum);
-    }
-
-    for (i = 0; i < message->num_options; i++) {
-        icmp_option_t *option = &message->options[i];
-        switch(option->type) {
-        case icmp_option_source_link_layer_address:
-            lladdr = &option->option.link_layer_address;
-            INFO("  source link layer address " PRI_MAC_ADDR_SRP, MAC_ADDR_PARAM_SRP(lladdr->address));
-            break;
-        case icmp_option_target_link_layer_address:
-            lladdr = &option->option.link_layer_address;
-            INFO("  destination link layer address " PRI_MAC_ADDR_SRP, MAC_ADDR_PARAM_SRP(lladdr->address));
-            break;
-        case icmp_option_prefix_information:
-            prefix_info = &option->option.prefix_information;
-            SEGMENTED_IPv6_ADDR_GEN_SRP(prefix_info->prefix.s6_addr, prefix_buf);
-            INFO("  prefix info: " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d %x %" PRIu32 " %" PRIu32,
-                 SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix_info->prefix.s6_addr, prefix_buf), prefix_info->length,
-                 prefix_info->flags, prefix_info->valid_lifetime, prefix_info->preferred_lifetime);
-            break;
-        case icmp_option_route_information:
-            route_info = &option->option.route_information;
-                SEGMENTED_IPv6_ADDR_GEN_SRP(route_info->prefix.s6_addr, router_prefix_buf);
-            INFO("  route info: " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d %x %d",
-                 SEGMENTED_IPv6_ADDR_PARAM_SRP(route_info->prefix.s6_addr, router_prefix_buf), route_info->length,
-                 route_info->flags, route_info->route_lifetime);
-            break;
-        case icmp_option_ra_flags_extension:
-            flags = option->option.ra_flags_extension;
-            INFO("  ra flags extension: %x %x %x %x %x %x", flags[0], flags[1], flags[2], flags[3], flags[4], flags[5]);
-            break;
-        default:
-            INFO("  option type %d", option->type);
-            break;
-        }
-    }
-}
-
-static bool
-icmp_message_parse_options(icmp_message_t *message, uint8_t *icmp_buf, unsigned length, unsigned *offset)
-{
-    uint8_t option_type, option_length_8;
-    unsigned option_length;
-    unsigned scan_offset = *offset;
-    icmp_option_t *option;
-    uint32_t reserved32;
-    prefix_information_t *prefix_information;
-    route_information_t *route_information;
-
-    int prefix_bytes;
-
-    // Count the options and validate the lengths
-    message->num_options = 0;
-    while (scan_offset < length) {
-        if (!dns_u8_parse(icmp_buf, length, &scan_offset, &option_type)) {
-            return false;
-        }
-        if (!dns_u8_parse(icmp_buf, length, &scan_offset, &option_length_8)) {
-            return false;
-        }
-        if (option_length_8 == 0) { // RFC4191 section 4.6: The value 0 is invalid.
-            ERROR("icmp_option_parse: option type %d length 0 is invalid.", option_type);
-            return false;
-        }
-        if (scan_offset + option_length_8 * 8 - 2 > length) {
-            ERROR("icmp_option_parse: option type %d length %d is longer than remaining available space %u",
-                  option_type, option_length_8 * 8, length - scan_offset + 2);
-            return false;
-        }
-        scan_offset += option_length_8 * 8 - 2;
-        message->num_options++;
-    }
-    // If there are no options, we're done. No options is valid, so return true.
-    if (message->num_options == 0) {
-        return true;
-    }
-    message->options = calloc(message->num_options, sizeof(*message->options));
-    if (message->options == NULL) {
-        ERROR("No memory for icmp options.");
-        return false;
-    }
-    option = message->options;
-    while (*offset < length) {
-        scan_offset = *offset;
-        if (!dns_u8_parse(icmp_buf, length, &scan_offset, &option_type)) {
-            return false;
-        }
-        if (!dns_u8_parse(icmp_buf, length, &scan_offset, &option_length_8)) {
-            return false;
-        }
-        // We already validated the length in the previous pass.
-        option->type = option_type;
-        option_length = option_length_8 * 8;
-
-        switch(option_type) {
-        case icmp_option_source_link_layer_address:
-        case icmp_option_target_link_layer_address:
-            // At this juncture we are assuming that everything we care about looks like an
-            // ethernet interface.  So for this case, length should be 8.
-            if (option_length != 8) {
-                INFO("Ignoring unexpectedly long link layer address: %d", option_length);
-                // Don't store the option.
-                message->num_options--;
-                *offset += option_length;
-                continue;
-            }
-            option->option.link_layer_address.length = 6;
-            memcpy(option->option.link_layer_address.address, &icmp_buf[scan_offset], 6);
-            break;
-        case icmp_option_prefix_information:
-            prefix_information = &option->option.prefix_information;
-            // Only a length of 32 is valid.  This is an invalid ICMP packet, not just misunderunderstood
-            if (option_length != 32) {
-                return false;
-            }
-            // prefix length 8
-            if (!dns_u8_parse(icmp_buf, length, &scan_offset, &prefix_information->length)) {
-                return false;
-            }
-            // flags 8a
-            if (!dns_u8_parse(icmp_buf, length, &scan_offset, &prefix_information->flags)) {
-                return false;
-            }
-            // valid lifetime 32
-            if (!dns_u32_parse(icmp_buf, length, &scan_offset,
-                               &prefix_information->valid_lifetime)) {
-                return false;
-            }
-            // preferred lifetime 32
-            if (!dns_u32_parse(icmp_buf, length, &scan_offset,
-                               &prefix_information->preferred_lifetime)) {
-                return false;
-            }
-            // reserved2 32
-            if (!dns_u32_parse(icmp_buf, length, &scan_offset, &reserved32)) {
-                return false;
-            }
-            // prefix 128
-            in6prefix_copy_from_data(&prefix_information->prefix, &icmp_buf[scan_offset], 16);
-            break;
-        case icmp_option_route_information:
-            route_information = &option->option.route_information;
-
-            // route length 8
-            if (!dns_u8_parse(icmp_buf, length, &scan_offset, &route_information->length)) {
-                return false;
-            }
-            switch(option_length) {
-            case 8:
-                prefix_bytes = 0;
-                break;
-            case 16:
-                prefix_bytes = 8;
-                break;
-            case 24:
-                prefix_bytes = 16;
-                break;
-            default:
-                ERROR("invalid route information option length %d for route length %d",
-                      option_length, route_information->length);
-                return false;
-            }
-            // flags 8
-            if (!dns_u8_parse(icmp_buf, length, &scan_offset, &route_information->flags)) {
-                return false;
-            }
-            // route lifetime 32
-            if (!dns_u32_parse(icmp_buf, length, &scan_offset, &route_information->route_lifetime)) {
-                return false;
-            }
-            // route (64, 96 or 128)
-            in6prefix_copy_from_data(&route_information->prefix, &icmp_buf[scan_offset], prefix_bytes);
-            break;
-        case icmp_option_ra_flags_extension:
-            // The RA Flags extension as defined in RFC 5175 must have a length of 1 (meaning 8 bytes).
-            // It's possible that a later spec will define a length > 1, but since we are implementing
-            // RFC5175, we are required to silently ignore anything after the first 8 bytes. Since
-            // we've already checked for length=0 (invalid), we can just take our six bytes of flags
-            // and not bounds-check further.
-            memcpy(option->option.ra_flags_extension, &icmp_buf[scan_offset], sizeof(option->option.ra_flags_extension));
-            break;
-        default:
-        case icmp_option_mtu:
-        case icmp_option_redirected_header:
-            // don't care
-            break;
-        }
-        *offset += option_length;
-        option++;
-    }
-    return true;
-}
-
-
-static void
 interface_prefix_deconfigure(void *context)
 {
     interface_t *interface = context;
@@ -938,6 +681,9 @@
             for (i = 0; i < router->num_options; i++, option++) {
                 if (option->type == icmp_option_prefix_information) {
                     prefix_information_t *prefix = &option->option.prefix_information;
+#ifndef RA_TESTER
+                    omr_publisher_check_prefix(route_state->omr_publisher, &prefix->prefix, prefix->length);
+#endif
                     if (prefix_usable(interface, route_state, router, prefix)) {
                         // We don't consider the prefix we would advertise to be infrastructure-provided if we see it
                         // advertised by another router, because that router is also a Thread BR, and we don't want
@@ -1290,6 +1036,10 @@
         // Mark routers from which we received neighbor advertises during the probe as reachable. Routers
         // that did not respond are no longer reachable.
         for (icmp_message_t *router = interface->routers; router != NULL; router = router->next) {
+            SEGMENTED_IPv6_ADDR_GEN_SRP(router->source.s6_addr, router_src_addr_buf);
+            INFO("router (%p) " PRI_SEGMENTED_IPv6_ADDR_SRP " was " PUB_S_SRP "reached during probing.", router,
+                 SEGMENTED_IPv6_ADDR_PARAM_SRP(router->source.s6_addr, router_src_addr_buf),
+                 router->reached ? "" : "not ");
             router->reachable = router->reached;
         }
         routing_policy_evaluate(interface, false);
@@ -1345,12 +1095,13 @@
     }
 }
 
-static void
+void
 router_solicit(icmp_message_t *message)
 {
     interface_t *iface, *interface;
     bool is_retransmission = false;
 
+
     // Further validate the message
     if (message->hop_limit != 255 || message->code != 0) {
         ERROR("Invalid router solicitation, hop limit = %d, code = %d", message->hop_limit, message->code);
@@ -1440,7 +1191,7 @@
     }
 }
 
-static void
+void
 router_advertisement(icmp_message_t *message)
 {
     interface_t *iface;
@@ -1490,6 +1241,7 @@
     // neighbor solicit.
     message->latest_na = message->received_time;
     message->reachable = true;
+    message->reached = true;
 
     // Check for the stub router flag here so that we have it when scanning PIOs for usability.
     for (int i = 0; i < message->num_options; i++) {
@@ -1504,7 +1256,7 @@
     routing_policy_evaluate(message->interface, false);
 }
 
-static void
+void
 neighbor_advertisement(icmp_message_t *message)
 {
     if (message->hop_limit != 255 || message->code != 0) {
@@ -1517,159 +1269,21 @@
     // prefix.
     for (icmp_message_t *router = message->interface->routers; router != NULL; router = router->next) {
         if (!in6addr_compare(&message->source, &router->source)) {
+            // Only log for usable routers, to avoid a lot of extra noise. However, we don't actually probe routers that
+            // aren't usable, so generally speaking this test will always be true.
             if (router->usable) {
                 SEGMENTED_IPv6_ADDR_GEN_SRP(message->source.s6_addr, source_buf);
                 INFO("usable neighbor advertisement recieved on " PUB_S_SRP " from " PRI_SEGMENTED_IPv6_ADDR_SRP,
                      message->interface->name, SEGMENTED_IPv6_ADDR_PARAM_SRP(message->source.s6_addr, source_buf));
-                router->latest_na = ioloop_timenow();
-                router->reached = true;
-                return;
-            } else {
-                router->latest_na = ioloop_timenow();
-                router->reached = true;
-                return;
             }
+            router->latest_na = ioloop_timenow();
+            router->reached = true;
+            router->reachable = true;
         }
     }
     return;
 }
 
-static void
-icmp_message(route_state_t *route_state, uint8_t *icmp_buf, unsigned length, int ifindex, int hop_limit, addr_t *src, addr_t *dest)
-{
-    unsigned offset = 0;
-    uint32_t reserved32;
-    interface_t *interface;
-    icmp_message_t *message = calloc(1, sizeof(*message));
-    if (message == NULL) {
-        ERROR("Unable to allocate icmp_message_t for parsing");
-        return;
-    }
-
-    message->source = src->sin6.sin6_addr;
-    message->destination = dest->sin6.sin6_addr;
-    message->hop_limit = hop_limit;
-    for (interface = route_state->interfaces; interface; interface = interface->next) {
-        if (interface->index == ifindex) {
-            message->interface = interface;
-            break;
-        }
-    }
-    message->received_time = ioloop_timenow();
-    message->received_time_already_adjusted = false;
-    message->new_router = true;
-    message->route_state = route_state;
-
-    if (message->interface == NULL) {
-        SEGMENTED_IPv6_ADDR_GEN_SRP(message->source.s6_addr, src_buf);
-        SEGMENTED_IPv6_ADDR_GEN_SRP(message->destination.s6_addr, dst_buf);
-        INFO("ICMP message type %d from " PRI_SEGMENTED_IPv6_ADDR_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP
-             " on interface index %d, which isn't listed.",
-             icmp_buf[0], SEGMENTED_IPv6_ADDR_PARAM_SRP(message->source.s6_addr, src_buf),
-             SEGMENTED_IPv6_ADDR_PARAM_SRP(message->destination.s6_addr, dst_buf), ifindex);
-        icmp_message_free(message);
-        return;
-    }
-
-    if (length < sizeof (struct icmp6_hdr)) {
-        ERROR("Short ICMP message: length %d is shorter than ICMP header length %zd", length, sizeof(struct icmp6_hdr));
-        icmp_message_free(message);
-        return;
-    }
-    INFO("length %d", length);
-
-    // The increasingly innaccurately named dns parse functions will work fine for this.
-    if (!dns_u8_parse(icmp_buf, length, &offset, &message->type)) {
-        goto out;
-    }
-    if (!dns_u8_parse(icmp_buf, length, &offset, &message->code)) {
-        goto out;
-    }
-    // XXX check the checksum
-    if (!dns_u16_parse(icmp_buf, length, &offset, &message->checksum)) {
-        goto out;
-    }
-    switch(message->type) {
-    case icmp_type_router_advertisement:
-        if (!dns_u8_parse(icmp_buf, length, &offset, &message->cur_hop_limit)) {
-            goto out;
-        }
-        if (!dns_u8_parse(icmp_buf, length, &offset, &message->flags)) {
-            goto out;
-        }
-        if (!dns_u16_parse(icmp_buf, length, &offset, &message->router_lifetime)) {
-            goto out;
-        }
-        if (!dns_u32_parse(icmp_buf, length, &offset, &message->reachable_time)) {
-            goto out;
-        }
-        if (!dns_u32_parse(icmp_buf, length, &offset, &message->retransmission_timer)) {
-            goto out;
-        }
-
-        if (!icmp_message_parse_options(message, icmp_buf, length, &offset)) {
-            goto out;
-        }
-        icmp_message_dump(message, &message->source, &message->destination);
-        router_advertisement(message);
-        // router_advertisement() is given ownership of the message
-        return;
-
-    case icmp_type_router_solicitation:
-        if (!dns_u32_parse(icmp_buf, length, &offset, &reserved32)) {
-            goto out;
-        }
-        if (!icmp_message_parse_options(message, icmp_buf, length, &offset)) {
-            goto out;
-        }
-        icmp_message_dump(message, &message->source, &message->destination);
-        router_solicit(message);
-        // router_solicit() is given ownership of the message.
-        return;
-
-    case icmp_type_neighbor_advertisement:
-        icmp_message_dump(message, &message->source, &message->destination);
-        neighbor_advertisement(message);
-        break;
-
-    case icmp_type_neighbor_solicitation:
-    case icmp_type_echo_request:
-    case icmp_type_echo_reply:
-    case icmp_type_redirect:
-        break;
-    }
-
-out:
-    icmp_message_free(message);
-    return;
-}
-
-#ifndef FUZZING
-static
-#endif
-void
-icmp_callback(io_t *NONNULL io, void *UNUSED context)
-{
-    ssize_t rv;
-    uint8_t icmp_buf[1500];
-    int ifindex = 0;
-    addr_t src, dest;
-    int hop_limit = 0;
-
-#ifndef FUZZING
-    rv = ioloop_recvmsg(io->fd, &icmp_buf[0], sizeof(icmp_buf), &ifindex, &hop_limit, &src, &dest);
-#else
-    rv = read(io->fd, &icmp_buf, sizeof(icmp_buf));
-#endif
-    if (rv < 0) {
-        ERROR("icmp_callback: can't read ICMP message: " PUB_S_SRP, strerror(errno));
-        return;
-    }
-    for (route_state_t *route_state = route_states; route_state != NULL; route_state = route_state->next) {
-        icmp_message(route_state, icmp_buf, (unsigned)rv, ifindex, hop_limit, &src, &dest); // rv will never be > sizeof(icmp_buf)
-    }
-}
-
 #if   defined(CONFIGURE_STATIC_INTERFACE_ADDRESSES_WITH_IPCONFIG) || \
       defined(CONFIGURE_STATIC_INTERFACE_ADDRESSES_WITH_IFCONFIG)
 static void
@@ -1860,366 +1474,6 @@
 #endif // THREAD_BORDER_ROUTRER && !RA_TESTER
 
 static void
-route_information_to_wire(dns_towire_state_t *towire, void *prefix_data,
-                          const char *source_interface, const char *dest_interface)
-{
-    uint8_t *prefix = prefix_data;
-
-#ifndef ND_OPT_ROUTE_INFORMATION
-#define ND_OPT_ROUTE_INFORMATION 24
-#endif
-    dns_u8_to_wire(towire, ND_OPT_ROUTE_INFORMATION);
-    dns_u8_to_wire(towire, 2); // length / 8
-    dns_u8_to_wire(towire, 64); // Interface prefixes are always 64 bits
-    dns_u8_to_wire(towire, 0); // There's no reason at present to prefer one Thread BR over another
-    dns_u32_to_wire(towire, BR_PREFIX_LIFETIME); // Route lifetime 1800 seconds (30 minutes)
-    dns_rdata_raw_data_to_wire(towire, prefix, 8); // /64 requires 8 bytes.
-    SEGMENTED_IPv6_ADDR_GEN_SRP(prefix, thread_prefix_buf);
-    INFO("Sending route to " PRI_SEGMENTED_IPv6_ADDR_SRP "%%" PUB_S_SRP " on " PUB_S_SRP,
-         SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix, thread_prefix_buf), source_interface, dest_interface);
-}
-
-static void
-router_advertisement_send(interface_t *interface, const struct in6_addr *destination)
-{
-    uint8_t *message;
-    dns_towire_state_t towire;
-    route_state_t *route_state = interface->route_state;
-
-    // Thread blocks RAs so no point sending them.
-    if (interface->inactive
-#ifndef RA_TESTER
-        || interface->is_thread
-#endif
-        ) {
-        return;
-    }
-
-#define MAX_ICMP_MESSAGE 1280
-    message = malloc(MAX_ICMP_MESSAGE);
-    if (message == NULL) {
-        ERROR("router_advertisement_send: unable to construct ICMP Router Advertisement: no memory");
-        return;
-    }
-
-    // Construct the ICMP header and options for each interface.
-    memset(&towire, 0, sizeof towire);
-    towire.p = message;
-    towire.lim = message + MAX_ICMP_MESSAGE;
-
-    // Construct the ICMP header.
-    // We use the DNS message construction functions because it's easy; probably should just make
-    // the towire functions more generic.
-    dns_u8_to_wire(&towire, ND_ROUTER_ADVERT);  // icmp6_type
-    dns_u8_to_wire(&towire, 0);                 // icmp6_code
-    dns_u16_to_wire(&towire, 0);                // The kernel computes the checksum (we don't technically have it).
-    dns_u8_to_wire(&towire, 0);                 // Hop limit, we don't set.
-    dns_u8_to_wire(&towire, 0);                 // Flags.  We don't offer DHCP, so We set neither the M nor the O bit.
-    // We are not a home agent, so no H bit.  Lifetime is 0, so Prf is 0.
-#ifdef ROUTER_LIFETIME_HACK
-    dns_u16_to_wire(&towire, BR_PREFIX_LIFETIME); // Router lifetime, hacked.  This shouldn't ever be enabled.
-#else
-#ifdef RA_TESTER
-    // Advertise a default route on the simulated thread network
-    if (!strcmp(interface->name, route_state->thread_interface_name)) {
-        dns_u16_to_wire(&towire, BR_PREFIX_LIFETIME); // Router lifetime for default route
-    } else {
-#endif
-        dns_u16_to_wire(&towire, 0);            // Router lifetime for non-default default route(s).
-#ifdef RA_TESTER
-    }
-#endif // RA_TESTER
-#endif // ROUTER_LIFETIME_HACK
-    dns_u32_to_wire(&towire, 0);                // Reachable time for NUD, we have no opinion on this.
-    dns_u32_to_wire(&towire, 0);                // Retransmission timer, again we have no opinion.
-
-    // Send Source link-layer address option
-    if (interface->have_link_layer_address) {
-        dns_u8_to_wire(&towire, ND_OPT_SOURCE_LINKADDR);
-        dns_u8_to_wire(&towire, 1); // length / 8
-        dns_rdata_raw_data_to_wire(&towire, &interface->link_layer, sizeof(interface->link_layer));
-        INFO("advertising source lladdr " PRI_MAC_ADDR_SRP
-             " on " PUB_S_SRP, MAC_ADDR_PARAM_SRP(interface->link_layer), interface->name);
-    }
-
-#ifndef RA_TESTER
-    // Send MTU of 1280 for Thread?
-    if (interface->is_thread) {
-        dns_u8_to_wire(&towire, ND_OPT_MTU);
-        dns_u8_to_wire(&towire, 1); // length / 8
-        dns_u32_to_wire(&towire, 1280);
-        INFO("advertising MTU of 1280 on " PUB_S_SRP, interface->name);
-    }
-#endif
-
-    // Send Prefix Information option if there's no IPv6 on the link.
-    if (interface->our_prefix_advertised && !interface->suppress_ipv6_prefix && route_state->have_xpanid_prefix) {
-        dns_u8_to_wire(&towire, ND_OPT_PREFIX_INFORMATION);
-        dns_u8_to_wire(&towire, 4); // length / 8
-        dns_u8_to_wire(&towire, 64); // On-link prefix is always 64 bits
-        dns_u8_to_wire(&towire, ND_OPT_PI_FLAG_ONLINK | ND_OPT_PI_FLAG_AUTO); // On link, autoconfig
-        dns_u32_to_wire(&towire, interface->valid_lifetime);
-        dns_u32_to_wire(&towire, interface->preferred_lifetime);
-        dns_u32_to_wire(&towire, 0); // Reserved
-        dns_rdata_raw_data_to_wire(&towire, &interface->ipv6_prefix, sizeof interface->ipv6_prefix);
-        SEGMENTED_IPv6_ADDR_GEN_SRP(interface->ipv6_prefix.s6_addr, ipv6_prefix_buf);
-        INFO("advertising on-link prefix " PRI_SEGMENTED_IPv6_ADDR_SRP " on " PUB_S_SRP,
-             SEGMENTED_IPv6_ADDR_PARAM_SRP(interface->ipv6_prefix.s6_addr, ipv6_prefix_buf), interface->name);
-
-    }
-
-    // In principle we can either send routes to links that are reachable by this router,
-    // or just advertise a router to the entire ULA /48.   In theory it doesn't matter
-    // which we do; if we support HNCP at some point we probably need to be specific, but
-    // for now being general is fine because we have no way to share a ULA.
-    // Unfortunately, some RIO implementations do not work with specific routes, so for now
-    // We are doing it the easy way and just advertising the /48.
-#define SEND_INTERFACE_SPECIFIC_RIOS 1
-#ifdef SEND_INTERFACE_SPECIFIC_RIOS
-
-    // If neither ROUTE_BETWEEN_NON_THREAD_LINKS nor RA_TESTER are defined, then we never want to
-    // send an RIO other than for the thread network prefix.
-#if defined (ROUTE_BETWEEN_NON_THREAD_LINKS) || defined(RA_TESTER)
-    interface_t *ifroute;
-    // Send Route Information option for other interfaces.
-    for (ifroute = route_state->interfaces; ifroute; ifroute = ifroute->next) {
-        if (ifroute->inactive) {
-            continue;
-        }
-        if (want_routing(route_state) &&
-            ifroute->our_prefix_advertised &&
-#ifdef SEND_ON_LINK_ROUTE
-            // In theory we don't want to send RIO for the on-link prefix, but there's this bug, see.
-            true &&
-#else
-            ifroute != interface &&
-#endif
-#ifdef RA_TESTER
-            // For the RA tester, we don't need to send an RIO to the thread network because we're the
-            // default router for that network.
-            strcmp(interface->name, route_state->thread_interface_name)
-#else
-            true
-#endif
-            )
-        {
-            route_information_to_wire(&towire, &ifroute->ipv6_prefix, ifroute->name, interface->name);
-        }
-    }
-#endif // ROUTE_BETWEEN_NON_THREAD_LINKS || RA_TESTER
-
-#ifndef RA_TESTER
-    // Send route information option for thread prefix
-    if (route_state->omr_watcher != NULL) {
-        omr_prefix_t *thread_prefixes = omr_watcher_prefixes_get(route_state->omr_watcher);
-
-        // Send RIOs for any other prefixes that appear on the Thread network
-        for (struct omr_prefix *prefix = thread_prefixes; prefix != NULL; prefix = prefix->next) {
-            route_information_to_wire(&towire, &prefix->prefix, route_state->thread_interface_name, interface->name);
-        }
-    }
-#endif
-#else
-#ifndef SKIP_SLASH_48
-    dns_u8_to_wire(&towire, ND_OPT_ROUTE_INFORMATION);
-    dns_u8_to_wire(&towire, 3); // length / 8
-    dns_u8_to_wire(&towire, 48); // ULA prefixes are always 48 bits
-    dns_u8_to_wire(&towire, 0); // There's no reason at present to prefer one Thread BR over another
-    dns_u32_to_wire(&towire, BR_PREFIX_LIFETIME); // Route lifetime 1800 seconds (30 minutes)
-    dns_rdata_raw_data_to_wire(&towire, &route_state->srp_server->ula_prefix, 16); // /48 requires 16 bytes
-#endif // SKIP_SLASH_48
-#endif // SEND_INTERFACE_SPECIFIC_RIOS
-
-    // Send the stub router flag
-    dns_u8_to_wire(&towire, ND_OPT_RA_FLAGS_EXTENSION);
-    dns_u8_to_wire(&towire, 1); // length / 8
-    dns_u8_to_wire(&towire, RA_FLAGS1_STUB_ROUTER);
-    dns_u8_to_wire(&towire, 0); // Five bytes of zero flag bits
-    dns_u32_to_wire(&towire, 0);
-
-    if (towire.error) {
-        ERROR("No space in ICMP output buffer for " PUB_S_SRP " at route.c:%d", interface->name, towire.line);
-        towire.error = 0;
-    } else {
-        SEGMENTED_IPv6_ADDR_GEN_SRP(destination->s6_addr, destination_buf);
-        INFO("sending advertisement to " PRI_SEGMENTED_IPv6_ADDR_SRP " on " PUB_S_SRP,
-             SEGMENTED_IPv6_ADDR_PARAM_SRP(destination->s6_addr, destination_buf),
-             interface->name);
-        icmp_send(message, towire.p - message, interface, destination);
-    }
-    free(message);
-}
-
-static void
-router_solicit_send(interface_t *interface)
-{
-    uint8_t *message;
-    dns_towire_state_t towire;
-
-    // Thread blocks RSs so no point sending them.
-    if (interface->inactive
-#ifndef RA_TESTER
-        || interface->is_thread
-#endif
-        ) {
-        return;
-    }
-
-#define MAX_ICMP_MESSAGE 1280
-    message = malloc(MAX_ICMP_MESSAGE);
-    if (message == NULL) {
-        ERROR("Unable to construct ICMP Router Advertisement: no memory");
-        return;
-    }
-
-    // Construct the ICMP header and options for each interface.
-    memset(&towire, 0, sizeof towire);
-    towire.p = message;
-    towire.lim = message + MAX_ICMP_MESSAGE;
-
-    // Construct the ICMP header.
-    // We use the DNS message construction functions because it's easy; probably should just make
-    // the towire functions more generic.
-    dns_u8_to_wire(&towire, ND_ROUTER_SOLICIT);  // icmp6_type
-    dns_u8_to_wire(&towire, 0);                  // icmp6_code
-    dns_u16_to_wire(&towire, 0);                 // The kernel computes the checksum (we don't technically have it).
-    dns_u32_to_wire(&towire, 0);                 // Reserved32
-
-    // Send Source link-layer address option
-    if (interface->have_link_layer_address) {
-        dns_u8_to_wire(&towire, ND_OPT_SOURCE_LINKADDR);
-        dns_u8_to_wire(&towire, 1); // length / 8
-        dns_rdata_raw_data_to_wire(&towire, &interface->link_layer, sizeof(interface->link_layer));
-    }
-
-    if (towire.error) {
-        ERROR("No space in ICMP output buffer for " PUB_S_SRP " at route.c:%d", interface->name, towire.line);
-    } else {
-        icmp_send(message, towire.p - message, interface, &in6addr_linklocal_allrouters);
-    }
-    free(message);
-}
-
-static void
-neighbor_solicit_send(interface_t *interface, struct in6_addr *destination)
-{
-    uint8_t *message;
-    dns_towire_state_t towire;
-
-#define MAX_ICMP_MESSAGE 1280
-    message = malloc(MAX_ICMP_MESSAGE);
-    if (message == NULL) {
-        ERROR("Unable to construct ICMP Router Advertisement: no memory");
-        return;
-    }
-
-    // Construct the ICMP header and options for each interface.
-    memset(&towire, 0, sizeof towire);
-    towire.p = message;
-    towire.lim = message + MAX_ICMP_MESSAGE;
-
-    // Construct the ICMP header.
-    // We use the DNS message construction functions because it's easy; probably should just make
-    // the towire functions more generic.
-    dns_u8_to_wire(&towire, ND_NEIGHBOR_SOLICIT);  // icmp6_type
-    dns_u8_to_wire(&towire, 0);                    // icmp6_code
-    dns_u16_to_wire(&towire, 0);                   // The kernel computes the checksum (we don't technically have it).
-    dns_u32_to_wire(&towire, 0);                   // Reserved32
-    dns_rdata_raw_data_to_wire(&towire, destination, sizeof(*destination)); // Target address of solicit
-
-    // Send Source link-layer address option
-    if (interface->have_link_layer_address) {
-        dns_u8_to_wire(&towire, ND_OPT_SOURCE_LINKADDR);
-        dns_u8_to_wire(&towire, 1); // length / 8
-        dns_rdata_raw_data_to_wire(&towire, &interface->link_layer, sizeof(interface->link_layer));
-    }
-
-    if (towire.error) {
-        ERROR("No space in ICMP output buffer for " PUB_S_SRP " at route.c:%d", interface->name, towire.line);
-    } else {
-        SEGMENTED_IPv6_ADDR_GEN_SRP(destination, dest_buf);
-        INFO("sending neighbor solicit on " PUB_S_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP,
-             interface->name, SEGMENTED_IPv6_ADDR_PARAM_SRP(destination, dest_buf));
-        icmp_send(message, towire.p - message, interface, destination);
-    }
-    free(message);
-}
-
-static void
-icmp_send(uint8_t *message, size_t length, interface_t *interface, const struct in6_addr *destination)
-{
-#ifdef FUZZING
-    char buffer[length];
-    memcpy(buffer, message, length);
-    return;
-#endif
-    struct iovec iov;
-    struct in6_pktinfo *packet_info;
-    socklen_t cmsg_length = CMSG_SPACE(sizeof(*packet_info)) + CMSG_SPACE(sizeof (int));
-    uint8_t *cmsg_buffer;
-    struct msghdr msg_header;
-    struct cmsghdr *cmsg_pointer;
-    int hop_limit = 255;
-    ssize_t rv;
-    struct sockaddr_in6 dest;
-
-    // Make space for the control message buffer.
-    cmsg_buffer = calloc(1, cmsg_length);
-    if (cmsg_buffer == NULL) {
-        ERROR("Unable to construct ICMP Router Advertisement: no memory");
-        return;
-    }
-
-    // Send the message
-    memset(&dest, 0, sizeof(dest));
-    dest.sin6_family = AF_INET6;
-    dest.sin6_scope_id = interface->index;
-#ifndef NOT_HAVE_SA_LEN
-    dest.sin6_len = sizeof(dest);
-#endif
-    msg_header.msg_namelen = sizeof(dest);
-    dest.sin6_addr = *destination;
-
-    msg_header.msg_name = &dest;
-    iov.iov_base = message;
-    iov.iov_len = length;
-    msg_header.msg_iov = &iov;
-    msg_header.msg_iovlen = 1;
-    msg_header.msg_control = cmsg_buffer;
-    msg_header.msg_controllen = cmsg_length;
-
-    // Specify the interface
-    cmsg_pointer = CMSG_FIRSTHDR(&msg_header);
-    cmsg_pointer->cmsg_level = IPPROTO_IPV6;
-    cmsg_pointer->cmsg_type = IPV6_PKTINFO;
-    cmsg_pointer->cmsg_len = CMSG_LEN(sizeof(*packet_info));
-    packet_info = (struct in6_pktinfo *)CMSG_DATA(cmsg_pointer);
-    memset(packet_info, 0, sizeof(*packet_info));
-    packet_info->ipi6_ifindex = interface->index;
-
-    // Router advertisements and solicitations have a hop limit of 255
-    cmsg_pointer = CMSG_NXTHDR(&msg_header, cmsg_pointer);
-    cmsg_pointer->cmsg_level = IPPROTO_IPV6;
-    cmsg_pointer->cmsg_type = IPV6_HOPLIMIT;
-    cmsg_pointer->cmsg_len = CMSG_LEN(sizeof(int));
-    memcpy(CMSG_DATA(cmsg_pointer), &hop_limit, sizeof(hop_limit));
-
-    // Send it
-    rv = sendmsg(icmp_listener.io_state->fd, &msg_header, 0);
-    if (rv < 0) {
-        uint8_t *in6_addr_bytes = ((struct sockaddr_in6 *)(msg_header.msg_name))->sin6_addr.s6_addr;
-        SEGMENTED_IPv6_ADDR_GEN_SRP(in6_addr_bytes, in6_addr_buf);
-        ERROR("icmp_send: sending " PUB_S_SRP " to " PRI_SEGMENTED_IPv6_ADDR_SRP " on interface " PUB_S_SRP
-              " index %d: " PUB_S_SRP, message[0] == ND_ROUTER_SOLICIT ? "solicit" : "advertise",
-              SEGMENTED_IPv6_ADDR_PARAM_SRP(in6_addr_bytes, in6_addr_buf),
-              interface->name, interface->index, strerror(errno));
-    } else if ((size_t)rv != iov.iov_len) {
-        ERROR("icmp_send: short send to interface " PUB_S_SRP ": %zd < %zd", interface->name, rv, iov.iov_len);
-    }
-    free(cmsg_buffer);
-}
-
-static void
 post_solicit_policy_evaluate(void *context)
 {
     interface_t *interface = context;
@@ -2329,75 +1583,6 @@
     }
 }
 
-bool
-start_icmp_listener(void)
-{
-    int sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6);
-    int true_flag = 1;
-#ifdef CONFIGURE_STATIC_INTERFACE_ADDRESSES
-    int false_flag = 0;
-#endif
-    struct icmp6_filter filter;
-    ssize_t rv;
-
-    if (sock < 0) {
-        ERROR("Unable to listen for icmp messages: " PUB_S_SRP, strerror(errno));
-        close(sock);
-        return false;
-    }
-
-    // Only accept router advertisements and router solicits.
-    ICMP6_FILTER_SETBLOCKALL(&filter);
-    ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter);
-    ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter);
-    ICMP6_FILTER_SETPASS(ND_NEIGHBOR_ADVERT, &filter);
-    rv = setsockopt(sock, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter));
-    if (rv < 0) {
-        ERROR("Can't set IPV6_RECVHOPLIMIT: " PUB_S_SRP ".", strerror(errno));
-        close(sock);
-        return false;
-    }
-
-    // We want a source address and interface index
-    rv = setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO, &true_flag, sizeof(true_flag));
-    if (rv < 0) {
-        ERROR("Can't set IPV6_RECVPKTINFO: " PUB_S_SRP ".", strerror(errno));
-        close(sock);
-        return false;
-    }
-
-    // We need to be able to reject RAs arriving from off-link.
-    rv = setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &true_flag, sizeof(true_flag));
-    if (rv < 0) {
-        ERROR("Can't set IPV6_RECVHOPLIMIT: " PUB_S_SRP ".", strerror(errno));
-        close(sock);
-        return false;
-    }
-
-#ifdef CONFIGURE_STATIC_INTERFACE_ADDRESSES
-    // Prevent our router advertisements from updating our routing table.
-    rv = setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &false_flag, sizeof(false_flag));
-    if (rv < 0) {
-        ERROR("Can't set IPV6_RECVHOPLIMIT: " PUB_S_SRP ".", strerror(errno));
-        close(sock);
-        return false;
-    }
-#endif
-
-    icmp_listener.io_state = ioloop_file_descriptor_create(sock, NULL, NULL);
-    if (icmp_listener.io_state == NULL) {
-        ERROR("No memory for ICMP I/O structure.");
-        close(sock);
-        return false;
-    }
-
-    // Beacon out a router advertisement every three minutes.
-    icmp_listener.unsolicited_interval = 3 * 60 * 1000;
-    ioloop_add_reader(icmp_listener.io_state, icmp_callback);
-
-    return true;
-}
-
 static void
 router_solicit_callback(void *context)
 {
@@ -2436,51 +1621,6 @@
                           NULL, 128 + srp_random16() % 896);
 }
 
-static void
-icmp_interface_subscribe(interface_t *interface, bool added)
-{
-    struct ipv6_mreq req;
-    int rv;
-
-    if (icmp_listener.io_state == NULL) {
-        ERROR("Interface subscribe without ICMP listener.");
-        return;
-    }
-
-    memset(&req, 0, sizeof req);
-    if (interface->index == -1) {
-        ERROR("icmp_interface_subscribe called before interface index fetch for " PUB_S_SRP, interface->name);
-        return;
-    }
-
-    req.ipv6mr_multiaddr = in6addr_linklocal_allrouters;
-    req.ipv6mr_interface = interface->index;
-    rv = setsockopt(icmp_listener.io_state->fd, IPPROTO_IPV6, added ? IPV6_JOIN_GROUP : IPV6_LEAVE_GROUP, &req,
-                    sizeof req);
-    if (rv < 0) {
-        ERROR("Unable to " PUB_S_SRP " all-routers multicast group on " PUB_S_SRP ": " PUB_S_SRP,
-              added ? "join" : "leave", interface->name, strerror(errno));
-        return;
-    } else {
-        INFO(PUB_S_SRP "subscribed on interface " PUB_S_SRP, added ? "" : "un",
-             interface->name);
-    }
-
-    req.ipv6mr_multiaddr = in6addr_linklocal_allnodes;
-    req.ipv6mr_interface = interface->index;
-    rv = setsockopt(icmp_listener.io_state->fd, IPPROTO_IPV6, added ? IPV6_JOIN_GROUP : IPV6_LEAVE_GROUP, &req,
-                    sizeof req);
-    if (rv < 0) {
-        ERROR("Unable to " PUB_S_SRP " all-nodes multicast group on " PUB_S_SRP ": " PUB_S_SRP,
-              added ? "join" : "leave", interface->name, strerror(errno));
-        return;
-    } else {
-        INFO(PUB_S_SRP "subscribed on interface " PUB_S_SRP, added ? "" : "un",
-             interface->name);
-    }
-
-}
-
 static interface_t *
 find_interface(route_state_t *route_state, const char *name, int ifindex)
 {
@@ -2611,7 +1751,7 @@
         if (router_is_advertising(router, prefix, preflen)) {
             *rp = router->next;
             router->next = NULL;
-            free(router);
+            icmp_message_free(router);
         } else {
             rp = &router->next;
         }
@@ -2620,8 +1760,8 @@
 #endif // RA_TESTER
 
 static void
-ifaddr_callback(void *context, const char *name, const addr_t *address, const addr_t *mask,
-                unsigned flags, enum interface_address_change change)
+ifaddr_callback(srp_server_t *server_state, void *context, const char *name, const addr_t *address,
+                const addr_t *mask, unsigned flags, enum interface_address_change change)
 {
     char addrbuf[INET6_ADDRSTRLEN];
     const uint8_t *addrbytes, *maskbytes, *prefp;
@@ -2743,10 +1883,10 @@
                 if (!is_thread_interface) {
                     if (change == interface_address_added) {
                         if (!interface->inactive) {
-                            dnssd_proxy_ifaddr_callback(context, name, address, mask, flags, change);
+                            dnssd_proxy_ifaddr_callback(server_state, context, name, address, mask, flags, change);
                         }
                     } else { // change == interface_address_removed
-                        dnssd_proxy_ifaddr_callback(context, name, address, mask, flags, change);
+                        dnssd_proxy_ifaddr_callback(server_state, context, name, address, mask, flags, change);
                     }
                 }
 #endif // #if !defined(RA_TESTER) && (SRP_FEATURE_COMBINED_SRP_DNSSD_PROXY)
@@ -2759,9 +1899,13 @@
                 // that all the stale router information will be updated during the discovery, or flushed away. If all
                 // routers are flushed, then srp-mdns-proxy will advertise its own prefix and configure the new IPv6
                 // address.
-                if ((address->sa.sa_family == AF_INET || address->sa.sa_family == AF_INET6) &&
-                    change == interface_address_deleted)
+                if (address->sa.sa_family == AF_INET6 &&                                       // An IPv6 address
+                    change == interface_address_deleted &&                                     // went away
+                    in6prefix_compare(&address->sin6.sin6_addr, &interface->ipv6_prefix, 8) && // not one of ours
+                    !is_thread_mesh_synthetic_or_link_local(&address->sin6.sin6_addr))         // not link-local
                 {
+
+                    INFO("clearing router discovery complete flag because address deleted.");
 #ifdef VICARIOUS_ROUTER_DISCOVERY
                     INFO("making all routers stale and start router discovery due to removed address");
                     adjust_router_received_time(interface, ioloop_timenow(),
@@ -2814,8 +1958,17 @@
 #ifndef LINUX
     } else if (address->sa.sa_family == AF_LINK) {
         if (address->ether_addr.len == 6) {
-            memcpy(interface->link_layer, address->ether_addr.addr, 6);
-            interface->have_link_layer_address = true;
+            if (change != interface_address_deleted) {
+                memcpy(interface->link_layer, address->ether_addr.addr, 6);
+                INFO("setting link layer address for " PUB_S_SRP " to " PRI_MAC_ADDR_SRP, interface->name,
+                     MAC_ADDR_PARAM_SRP(interface->link_layer));
+                interface->have_link_layer_address = true;
+            } else {
+                INFO("resetting link layer address for " PUB_S_SRP " (was " PRI_MAC_ADDR_SRP ")", interface->name,
+                     MAC_ADDR_PARAM_SRP(interface->link_layer));
+                memset(interface->link_layer, 0, 6);
+                interface->have_link_layer_address = false;
+            }
         }
 #endif
     }
@@ -2868,9 +2021,9 @@
         ERROR("prefix syntax incorrect: " PRI_S_SRP, prefix_addr_string);
         goto fail;
     }
-    SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf);
+    SEGMENTED_IPv6_ADDR_GEN_SRP(route_state->thread_mesh_local_prefix.s6_addr, ml_prefix_buf);
     INFO(PRI_SEGMENTED_IPv6_ADDR_SRP PUB_S_SRP,
-         SEGMENTED_IPv6_ADDR_PARAM_SRP(route_state->thread_mesh_local_prefix.s6_addr, prefix_buf),
+         SEGMENTED_IPv6_ADDR_PARAM_SRP(route_state->thread_mesh_local_prefix.s6_addr, ml_prefix_buf),
          slash ? slash : "");
     route_state->have_mesh_local_prefix = true;
     return;
@@ -2890,7 +2043,7 @@
     for (interface = route_state->interfaces; interface != NULL; interface = interface->next) {
         interface->old_num_ipv6_addresses = interface->num_ipv6_addresses;
     }
-    ioloop_map_interface_addresses_here(&route_state->interface_addresses, NULL, route_state, ifaddr_callback);
+    ioloop_map_interface_addresses_here(route_state->srp_server, &route_state->interface_addresses, NULL, route_state, ifaddr_callback);
 
     for (interface = route_state->interfaces; interface; interface = interface->next) {
 #if defined(THREAD_BORDER_ROUTER) && !defined(RA_TESTER)
@@ -2919,6 +2072,7 @@
     } else if (!have_active && route_state->have_non_thread_interface) {
         INFO("we no longer have an active interface");
         route_state->have_non_thread_interface = false;
+        route_state->partition_can_advertise_service = false;
         // Stop advertising the service, if we are doing so.
         partition_discontinue_all_srp_service(route_state);
     }
@@ -3062,7 +2216,7 @@
 }
 
 static void
-cti_get_xpanid_callback(void *context, uint64_t new_xpanid, cti_status_t status)
+route_get_xpanid_callback(void *context, uint64_t new_xpanid, cti_status_t status)
 {
     route_state_t *route_state = context;
     if (status == kCTIStatus_Disconnected || status == kCTIStatus_DaemonNotRunning) {
@@ -3088,7 +2242,7 @@
     in6addr_zero(&route_state->xpanid_prefix);
     route_state->xpanid_prefix.s6_addr[0] = 0xfd;
     for (int i = 1; i < 8; i++) {
-        route_state->xpanid_prefix.s6_addr[i] = ((route_state->srp_server->xpanid >> ((7 - i) * 8)) & 0xFFU);
+        route_state->xpanid_prefix.s6_addr[i] = ((route_state->srp_server->xpanid >> ((8 - i) * 8)) & 0xFFU);
     }
     route_state->have_xpanid_prefix = true;
 
@@ -3197,9 +2351,18 @@
             num_prefixes++;
         }
         if (num_prefixes != route_state->num_thread_prefixes) {
+            int old_num_prefixes = route_state->num_thread_prefixes;
             INFO("%d prefixes instead of %d, evaluating policy", num_prefixes, route_state->num_thread_prefixes);
             routing_policy_evaluate_all_interfaces(route_state, true);
             route_state->num_thread_prefixes = num_prefixes;
+            if (old_num_prefixes == 0 && num_prefixes > 0) {
+                INFO("thread prefix available, may advertise anycast");
+                partition_maybe_advertise_anycast_service(route_state);
+            }
+            if (old_num_prefixes > 0 && num_prefixes == 0) {
+                INFO("all thread prefixes are gone, stop advertising anycast service");
+                partition_stop_advertising_anycast_service(route_state, route_state->thread_sequence_number);
+            }
         }
     }
 }
@@ -3231,15 +2394,14 @@
     }
     if (status == kCTIStatus_NoError) {
         status = cti_get_extended_pan_id(route_state->srp_server, &route_state->thread_xpanid_context, route_state,
-                                         cti_get_xpanid_callback, NULL);
+                                         route_get_xpanid_callback, NULL);
     }
     if (status == kCTIStatus_NoError) {
         status = cti_get_rloc16(route_state->srp_server, &route_state->thread_rloc16_context, route_state,
                                 route_rloc16_callback, NULL);
     }
     if (status == kCTIStatus_NoError) {
-        status = cti_get_mesh_local_prefix(route_state->srp_server,
-                                           &route_state->thread_ml_prefix_connection, route_state,
+        status = cti_get_mesh_local_prefix(route_state->srp_server, route_state,
                                            route_get_mesh_local_prefix_callback, NULL);
     }
     if (status != kCTIStatus_NoError) {
@@ -3273,13 +2435,6 @@
     omr_publisher_set_reconnect_callback(route_state->omr_publisher, attempt_wpan_reconnect);
     omr_publisher_start(route_state->omr_publisher);
     omr_watcher_start(route_state->omr_watcher);
-    route_state->route_tracker = route_tracker_create(route_state, "main");
-    if (route_state->route_tracker == NULL) {
-        ERROR("route_tracker create failed");
-        return;
-    }
-    route_tracker_set_reconnect_callback(route_state->route_tracker, attempt_wpan_reconnect);
-    route_tracker_start(route_state->route_tracker);
     route_state->thread_network_running = true;
 }
 #endif //  defined(THREAD_BORDER_ROUTER) && !defined(RA_TESTER)
@@ -3349,6 +2504,7 @@
         cti_events_discontinue(route_state->thread_ml_prefix_connection);
         route_state->thread_ml_prefix_connection = NULL;
     }
+    srp_mdns_flush(route_state->srp_server);
 #if SRP_FEATURE_REPLICATION
     INFO("stop srp replication.");
     srpl_shutdown(route_state->srp_server);
@@ -3358,6 +2514,7 @@
     nat64_stop(route_state);
 #endif
     partition_state_reset(route_state);
+    route_state->thread_network_running = false;
 }
 #endif // RA_TESTER
 
@@ -3460,8 +2617,6 @@
     if (route_state->service_set_changed_wakeup != NULL) {
         ioloop_cancel_wake_event(route_state->service_set_changed_wakeup);
     }
-
-    route_state->thread_network_running = false;
 }
 
 static void
@@ -3469,6 +2624,7 @@
 {
     srp_server_t *server_state = context;
     route_state_t *route_state = server_state->route_state;
+
     INFO("listening on port %d", port);
     route_state->srp_service_listen_port = port;
     if (route_state->have_non_thread_interface) {
@@ -3480,18 +2636,30 @@
 }
 
 static void
-partition_srp_listener_canceled(void *context)
+partition_srp_listener_canceled(comm_t *listener, void *context)
 {
     srp_server_t *server_state = context;
     route_state_t *route_state = server_state->route_state;
 
-    if (route_state->srp_listener != NULL) {
+    INFO("listener is %p", listener);
+    if (route_state->srp_listener == listener) {
         ioloop_comm_release(route_state->srp_listener);
         route_state->srp_listener = NULL;
-    }
 
-    if (!server_state->srp_unicast_service_blocked) {
-        partition_discontinue_srp_service(route_state);
+        if (!server_state->srp_unicast_service_blocked) {
+            partition_discontinue_srp_service(route_state);
+        }
+    }
+}
+
+static void
+partition_stop_srp_listener(route_state_t *route_state)
+{
+    if (route_state->srp_listener != NULL) {
+        INFO("discontinuing SRP service on port %d", route_state->srp_service_listen_port);
+        ioloop_listener_cancel(route_state->srp_listener);
+        ioloop_comm_release(route_state->srp_listener);
+        route_state->srp_listener = NULL;
     }
 }
 
@@ -3515,11 +2683,14 @@
         }
     }
 
+    // Make sure we don't overwrite the listener without stopping it.
+    partition_stop_srp_listener(route_state);
+
     INFO("starting listener.");
-    route_state->srp_listener = srp_proxy_listen(avoid_ports, num_avoid_ports, partition_proxy_listener_ready,
-                                                 partition_srp_listener_canceled, route_state->srp_server);
+    route_state->srp_listener = srp_proxy_listen(avoid_ports, num_avoid_ports, NULL, partition_proxy_listener_ready,
+                                                 partition_srp_listener_canceled, NULL, NULL, route_state->srp_server);
     if (route_state->srp_listener == NULL) {
-        ERROR("partition_start_srp_listener: Unable to start SRP Proxy listener, so can't advertise it");
+        ERROR("Unable to start SRP listener, so can't advertise it");
         return;
     }
 }
@@ -3527,17 +2698,11 @@
 void
 partition_discontinue_srp_service(route_state_t *route_state)
 {
-    if (route_state->srp_listener != NULL) {
-        INFO("discontinuing proxy service on port %d", route_state->srp_service_listen_port);
-        ioloop_listener_cancel(route_state->srp_listener);
-        ioloop_comm_release(route_state->srp_listener);
-        route_state->srp_listener = NULL;
-    }
+    partition_stop_srp_listener(route_state);
 
     // Won't match
     in6addr_zero(&route_state->srp_listener_ip_address);
     route_state->srp_service_listen_port = 0;
-    route_state->partition_can_advertise_service = false;
 
     // Stop advertising the service, if we are doing so.
     partition_stop_advertising_service(route_state);
@@ -3595,7 +2760,7 @@
     if (change != interface_address_deleted) {
         // If this address isn't an address we're already listening on, check if it's an anycast address; if so,
         // skip it as a candidate to listen on.
-        if (is_thread_mesh_anycast_address(addr)) {
+        if (is_thread_mesh_synthetic_address(addr)) {
             INFO(PRI_SEGMENTED_IPv6_ADDR_SRP ": thread anycast address.",
                  SEGMENTED_IPv6_ADDR_PARAM_SRP(addr, addr_buf));
         }
@@ -3728,6 +2893,23 @@
 #endif
 }
 
+static bool
+route_maybe_restart_service_tracker(route_state_t *route_state, int *reset, int *increment)
+{
+    *reset = 0;
+    (*increment)++;
+    if (*increment > 5) {
+        if (route_state->srp_server->service_tracker != NULL) {
+            service_tracker_start(route_state->srp_server->service_tracker);
+        } else {
+            FAULT("service tracker not present when restarting.");
+        }
+        *increment = 0;
+        return true;
+    }
+    return false;
+}
+
 static void
 partition_stop_advertising_service(route_state_t *route_state)
 {
@@ -3736,12 +2918,16 @@
     uint8_t service_info[] = { 0, 0, 0, 1 };
     int status;
 
+    if (route_maybe_restart_service_tracker(route_state, &route_state->times_advertised_unicast, &route_state->times_unadvertised_unicast)) {
+        INFO("restarted service tracker.");
+    }
     service_info[0] = THREAD_SRP_SERVER_OPTION & 255;
     status = cti_remove_service(route_state->srp_server, route_state, partition_remove_service_done, NULL,
                                 THREAD_ENTERPRISE_NUMBER, service_info, 1);
     if (status != kCTIStatus_NoError) {
         INFO("status %d", status);
     }
+    route_state->advertising_srp_unicast_service = false;
 }
 
 void
@@ -3752,6 +2938,9 @@
     uint8_t service_info[] = { 0, 0, 0, 1 };
     int status;
 
+    if (route_maybe_restart_service_tracker(route_state, &route_state->times_advertised_anycast, &route_state->times_unadvertised_anycast)) {
+        INFO("restarted service tracker.");
+    }
     service_info[0] = THREAD_SRP_SERVER_ANYCAST_OPTION & 255;
     service_info[1] = sequence_number;
     status = cti_remove_service(route_state->srp_server, route_state, partition_remove_service_done, NULL,
@@ -3760,6 +2949,12 @@
         INFO("status %d", status);
     }
     route_state->advertising_srp_anycast_service = false;
+    if (route_state->route_tracker != NULL) {
+        INFO("discontinuing route tracker");
+        route_tracker_cancel(route_state->route_tracker);
+        route_tracker_release(route_state->route_tracker);
+        route_state->route_tracker = NULL;
+    }
     route_refresh_interface_list(route_state);
 }
 
@@ -3781,6 +2976,9 @@
     uint8_t server_info[18];
     int ret;
 
+    if (route_maybe_restart_service_tracker(route_state, &route_state->times_unadvertised_unicast, &route_state->times_advertised_unicast)) {
+        INFO("restarted service tracker.");
+    }
     memcpy(&server_info, &route_state->srp_listener_ip_address, 16);
     server_info[16] = (route_state->srp_service_listen_port >> 8) & 255;
     server_info[17] = route_state->srp_service_listen_port & 255;
@@ -3800,6 +2998,7 @@
 
     // Wait a while for the service add to be reflected in an event.
     partition_schedule_service_add_wakeup(route_state);
+    route_state->advertising_srp_unicast_service = true;
 }
 
 static void
@@ -3808,6 +3007,9 @@
     uint8_t service_info[] = {0, 0, 0, 1};
     int ret;
 
+    if (route_maybe_restart_service_tracker(route_state, &route_state->times_unadvertised_anycast, &route_state->times_advertised_anycast)) {
+        INFO("restarted service tracker.");
+    }
     service_info[0] = THREAD_SRP_SERVER_ANYCAST_OPTION & 255;
     service_info[1] = route_state->thread_sequence_number;
     INFO("%" PRIu64 "/%02x/ %x", THREAD_ENTERPRISE_NUMBER, service_info[0], route_state->thread_sequence_number);
@@ -3822,6 +3024,18 @@
     partition_schedule_anycast_service_add_wakeup(route_state);
     route_state->advertising_srp_anycast_service = true;
     route_refresh_interface_list(route_state);
+
+    if (route_state->route_tracker == NULL) {
+        route_state->route_tracker = route_tracker_create(route_state, "main");
+        if (route_state->route_tracker == NULL) {
+            ERROR("route_tracker create failed");
+            return;
+        }
+        route_tracker_set_reconnect_callback(route_state->route_tracker, attempt_wpan_reconnect);
+        route_tracker_start(route_state->route_tracker);
+    } else {
+        INFO("route tracker already running.");
+    }
 }
 
 static void
@@ -3880,6 +3094,7 @@
     thread_service_t *service;
     int num_lower_services = 0;
     int num_other_services = 0;
+    int num_legacy_services = 0;
     int i;
     int64_t last_add_time;
     bool advertising_service = false;
@@ -3933,9 +3148,30 @@
             goto schedule_wakeup;
         }
 
+        // See if host advertising this unicast service is also advertising an anycast service; if not, then this
+        // unicast service doesn't count (much).
+        bool anycast_present = false;
+        for (thread_service_t *aservice = service_tracker_services_get(route_state->srp_server->service_tracker);
+             aservice != NULL; aservice = aservice->next)
+        {
+            if (aservice->ignore || aservice->service_type != anycast_service) {
+                continue;
+            }
+            if (service->rloc16 == aservice->rloc16) {
+                anycast_present = true;
+                break;
+            }
+        }
+        if (!anycast_present) {
+            num_legacy_services++;
+            route_state->seen_legacy_service = true;
+            continue;
+        }
+
         int cmp = in6addr_compare(&service->u.unicast.address, &route_state->srp_listener_ip_address);
         SEGMENTED_IPv6_ADDR_GEN_SRP(&service->u.unicast.address, addr_buf);
-        INFO(PRI_SEGMENTED_IPv6_ADDR_SRP ": is " PUB_S_SRP " listener address.",
+        INFO(PUB_S_SRP PRI_SEGMENTED_IPv6_ADDR_SRP ": is " PUB_S_SRP " listener address.",
+             anycast_present ? "legacy service " : "service ",
              SEGMENTED_IPv6_ADDR_PARAM_SRP(&service->u.unicast.address, addr_buf),
              cmp < 0 ? "less than" : cmp > 0 ? "greater than" : "equal to");
         if (cmp < 0) {
@@ -3949,8 +3185,9 @@
 
     // We only want to advertise our service if there are no services being advertised on addresses that are lower than
     // ours. Also, if we notice that a service is being advertised by our rloc16 with a different IP address than the
-    // listener address, it's a stale address, so remove it.
-    if (num_lower_services > 0) {
+    // listener address, it's a stale address, so remove it. If we have seen a legacy service, and there is only one
+    // other non-legacy service, continue to advertise a second service, since cooperating services are preferable.
+    if ((num_lower_services > 0  && !route_state->seen_legacy_service) || num_lower_services > 1) {
         if (advertising_service) {
             SEGMENTED_IPv6_ADDR_GEN_SRP(&route_state->srp_listener_ip_address, addr_buf);
             INFO(PRI_SEGMENTED_IPv6_ADDR_SRP ": stopping advertising unicast service.",
@@ -3961,9 +3198,13 @@
             INFO("not advertising unicast service.");
         }
     }
-    // If there is not some other service published, and we are not publishing, publish.
-    else if (num_other_services == 0) {
-        if (!advertising_service) {
+    // If there is not some other service published, and we are not publishing, publish.  If there is a legacy (no
+    // anycast) service published, publish a second service so as to encourage the legacy service to withdraw, because
+    // cooperating services are preferable to competing services.
+    else if (num_other_services < 1 || (num_other_services < 2 && route_state->seen_legacy_service)) {
+        if (num_legacy_services > 1 && num_other_services > 1) {
+            ERROR("%d legacy services present!", num_legacy_services);
+        } else if (!advertising_service) {
             SEGMENTED_IPv6_ADDR_GEN_SRP(&route_state->srp_listener_ip_address, addr_buf);
             INFO(PRI_SEGMENTED_IPv6_ADDR_SRP ": starting advertising unicast service.",
                  SEGMENTED_IPv6_ADDR_PARAM_SRP(&route_state->srp_listener_ip_address, addr_buf));
@@ -3991,6 +3232,11 @@
         return;
     }
 
+    if (!route_state->partition_can_advertise_service) {
+        INFO("service advertisements are blocked.");
+        return;
+    }
+
     if (!route_state->partition_can_advertise_anycast_service) {
         INFO("no service to advertise yet.");
         return;
@@ -4001,6 +3247,11 @@
         return;
     }
 
+    if (route_state->num_thread_prefixes == 0) {
+        INFO("OMR prefix is not yet advertised.");
+        return;
+    }
+
     // The add service function requires a remove prior to the add, so if we are doing an add, we need to wait
     // for things to stabilize before allowing the removal of a service to trigger a re-evaluation.
     // Therefore, if we've done an add in the past ten seconds, wait ten seconds before trying another add.
@@ -4201,15 +3452,6 @@
             omr_publisher_start(route_state->omr_publisher);
             omr_watcher_start(route_state->omr_watcher);
         }
-        if (route_state->route_tracker == NULL) {
-            route_state->route_tracker = route_tracker_create(route_state, "main");
-            if (route_state->route_tracker == NULL) {
-                ERROR("route_tracker create failed");
-                return;
-            }
-            route_tracker_set_reconnect_callback(route_state->route_tracker, attempt_wpan_reconnect);
-            route_tracker_start(route_state->route_tracker);
-        }
     } else {
         INFO("Not enabling service: " PUB_S_SRP,
              am_associated ? "associated" : "!associated");
@@ -4268,6 +3510,29 @@
 }
 
 #endif // RA_TESTER
+
+#if SRP_FEATURE_LOCAL_DISCOVERY
+int
+route_get_current_infra_interface_index(void)
+{
+    extern srp_server_t *srp_servers;
+    if (srp_servers == NULL) {
+        INFO("no SRP servers");
+        return -1;
+    }
+    route_state_t *route_state = srp_servers->route_state;
+    if (route_state == NULL) {
+        INFO("no route state");
+        return -1;
+    }
+    for (interface_t *interface = route_state->interfaces; interface != NULL; interface = interface->next) {
+        if (!interface->inactive && !interface->is_thread) {
+            return (int)interface->index; // real interface indexes are always positive integers
+        }
+    }
+    return -1;
+}
+#endif // SRP_FEATURE_LOCAL_DISCOVERY
 #endif // STUB_ROUTER
 
 // Local Variables:
diff --git a/ServiceRegistration/route.h b/ServiceRegistration/route.h
index e98d3ba..499d877 100644
--- a/ServiceRegistration/route.h
+++ b/ServiceRegistration/route.h
@@ -1,6 +1,6 @@
 /* route.h
  *
- * Copyright (c) 2019-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2019-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -345,6 +345,7 @@
     bool have_mesh_local_prefix;
     bool have_mesh_local_address;
     bool advertising_srp_anycast_service;
+    bool advertising_srp_unicast_service;
     bool have_proposed_srp_listener_address;
     bool seen_listener_address;
     struct in6_addr thread_mesh_local_prefix;
@@ -358,6 +359,8 @@
     int num_thread_interfaces; // Should be zero or one.
     int ula_serial;
     int num_thread_prefixes;
+    int times_advertised_unicast, times_advertised_anycast;
+    int times_unadvertised_unicast, times_unadvertised_anycast;
     subproc_t *NULLABLE thread_interface_enumerator_process;
     subproc_t *NULLABLE thread_prefix_adder_process;
     subproc_t *NULLABLE thread_rti_setter_process;
@@ -366,6 +369,7 @@
     char *NULLABLE thread_interface_name;
     char *NULLABLE home_interface_name;
     bool have_non_thread_interface;
+    bool seen_legacy_service;
 #if SRP_FEATURE_NAT64
     nat64_t *NULLABLE nat64;
 #endif
@@ -413,7 +417,7 @@
 #endif // RA_TESTER
 };
 
-extern srp_server_t *NONNULL srp_server; // temporary static srp server pointer
+extern route_state_t *NONNULL route_states; // same
 
 route_state_t *NULLABLE route_state_create(srp_server_t *NONNULL server_state, const char *NONNULL name);
 void route_ula_setup(route_state_t *NULLABLE route_state);
@@ -437,6 +441,12 @@
 #define interface_release(interface) interface_release_(interface, __FILE__, __LINE__)
 void interface_release_(interface_t *NONNULL interface, const char *NONNULL file, int line);
 void route_refresh_interface_list(route_state_t *NONNULL route_state);
+
+void router_solicit(icmp_message_t *NONNULL message);
+void router_advertisement(icmp_message_t *NONNULL message);
+void neighbor_advertisement(icmp_message_t *NONNULL message);
+
+int route_get_current_infra_interface_index(void);
 #endif // __SERVICE_REGISTRATION_ROUTE_H
 
 // Local Variables:
diff --git a/ServiceRegistration/service-publisher.c b/ServiceRegistration/service-publisher.c
index a91767f..ec19ccf 100644
--- a/ServiceRegistration/service-publisher.c
+++ b/ServiceRegistration/service-publisher.c
@@ -1,6 +1,6 @@
 /* service-publisher.c
  *
- * Copyright (c) 2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2023-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -64,16 +64,18 @@
 #include "srp.h"
 #include "dns-msg.h"
 #include "ioloop.h"
-#include "adv-ctl-server.h"
 #include "srp-crypto.h"
 
 #include "cti-services.h"
 #include "srp-gw.h"
 #include "srp-proxy.h"
 #include "srp-mdns-proxy.h"
+#include "adv-ctl-server.h"
 #include "dnssd-proxy.h"
 #include "srp-proxy.h"
 #include "route.h"
+#include "ifpermit.h"
+#include "srp-dnssd.h"
 
 #define STATE_MACHINE_IMPLEMENTATION 1
 typedef enum {
@@ -106,20 +108,33 @@
 // When the listener restarts, we don't actually want to wait.
 #define SERVICE_PUBLISHER_LISTENER_RESTART_WAIT 1
 
+#define ADDRESS_RECORD_TTL  4500
+#define OTHER_RECORD_TTL    4500
+
 struct service_publisher {
     int ref_count;
     state_machine_header_t state_header;
     char *id;
+    char *thread_interface_name;
     srp_server_t *server_state;
     wakeup_t *NULLABLE wakeup_timer;
+    wakeup_t *NULLABLE sed_timeout;
     comm_t *srp_listener;
     void (*reconnect_callback)(void *context);
     thread_service_t *published_unicast_service;
     thread_service_t *published_anycast_service;
     thread_service_t *publication_queue;
-    cti_connection_t ml_eid_connection;
+    cti_connection_t active_data_set_connection;
+    cti_connection_t wed_tracker_connection;
+    cti_connection_t neighbor_tracker_connection;
     struct in6_addr thread_mesh_local_address;
+    struct in6_addr wed_ml_eid;
+    struct in6_addr neighbor_ml_eid;
+    char *NULLABLE wed_ext_address_string;
+    char *NULLABLE wed_ml_eid_string;
+    char *NULLABLE neighbor_ml_eid_string;
     int startup_delay_range;
+    int retry_interval; // Retry interval in seconds for re-publishing service(s)
     uint16_t srp_listener_port;
     bool have_ml_eid;
     bool first_time;
@@ -127,12 +142,302 @@
     bool canceled;
     bool have_srp_listener;
     bool seen_service_list;
+    bool stopped;
+    bool have_thread_interface_name;
+    bool have_unicast_in_net_data, have_anycast_in_net_data;
+    bool cached_services_published, started_stale_service_timeout;
 };
 
 static uint64_t service_publisher_serial_number;
 
 static void service_publisher_queue_run(service_publisher_t *publisher);
 
+void
+service_publisher_unadvertise_all(service_publisher_t *publisher)
+{
+    srp_server_t *server_state = publisher->server_state;
+
+    publisher->cached_services_published = false;
+    for (adv_host_t *host = server_state->hosts; host; host = host->next) {
+        // If we have an outstanding update, finish it.
+        if (host->update != NULL) {
+            srp_mdns_update_finished(host->update);
+        }
+        if (host->addresses != NULL) {
+            for (int i = 0; i < host->addresses->num; i++) {
+                adv_record_t *record = host->addresses->vec[i];
+                if (record != NULL) {
+                    if (record->rrtype == dns_rrtype_aaaa && record->rdlen == 16) {
+                        SEGMENTED_IPv6_ADDR_GEN_SRP(record->rdata, rdata_buf);
+                        INFO("unadvertising " PRI_S_SRP " IN AAAA " PRI_SEGMENTED_IPv6_ADDR_SRP " rec %p rref %p",
+                             host->name, SEGMENTED_IPv6_ADDR_PARAM_SRP(record->rdata, rdata_buf),
+                            record, record->rref);
+                    }
+                    srp_mdns_shared_record_remove(server_state, record);
+                    // DNSServiceRemoveRecord should clear the cache, but it doesn't.
+                    DNSServiceReconfirmRecord(0, server_state->advertise_interface, host->name, record->rrtype,
+                                              dns_qclass_in, record->rdlen, record->rdata);
+                }
+            }
+        }
+        if (host->key_record != NULL) {
+            srp_mdns_shared_record_remove(server_state, host->key_record);
+        }
+        if (host->instances != NULL) {
+            for (int i = 0; i < host->instances->num; i++) {
+                adv_instance_t *instance = host->instances->vec[i];
+                if (instance != NULL && instance->txn != NULL) {
+                    INFO("unadvertising " PRI_S_SRP "." PRI_S_SRP " instance %p sdref %p",
+                         instance->instance_name, instance->service_type, instance, instance->txn->sdref);
+                    ioloop_dnssd_txn_cancel(instance->txn);
+                    ioloop_dnssd_txn_release(instance->txn);
+                    instance->txn = NULL;
+                }
+            }
+        }
+    }
+}
+
+static void
+service_publisher_instance_callback(DNSServiceRef UNUSED sdref, DNSServiceFlags UNUSED flags, DNSServiceErrorType error_code,
+                                    const char *name, const char *regtype, const char *domain, void *context)
+{
+    adv_instance_t *instance = context;
+    if (error_code != kDNSServiceErr_NoError) {
+        INFO("DNSServiceRegister failed: " PUB_S_SRP "." PUB_S_SRP " host " PRI_S_SRP ": %d (instance %p sdref %p)",
+             name, regtype, domain, error_code, instance, instance->txn == NULL ? 0 : instance->txn->sdref);
+        ioloop_dnssd_txn_cancel(instance->txn);
+        ioloop_dnssd_txn_release(instance->txn);
+        instance->txn = NULL;
+    } else {
+        INFO("DNSServiceRegister succeeded: " PUB_S_SRP "." PUB_S_SRP " host " PRI_S_SRP " (instance %p sdref %p)",
+             instance->instance_name, instance->service_type, instance->host->registered_name,
+             instance, instance->txn == NULL ? 0 : instance->txn->sdref);
+    }
+}
+
+static void
+service_publisher_re_advertise_instance(srp_server_t *server_state, adv_host_t *host, adv_instance_t *instance)
+{
+    DNSServiceRef service_ref = server_state->shared_registration_txn->sdref;
+
+    // Make sure we don't double-register.
+    if (instance->txn != NULL) {
+        if (instance->txn->sdref != NULL) {
+            if (instance->shared_txn == (intptr_t)server_state->shared_registration_txn) {
+                INFO("instance is already registered: " PUB_S_SRP "." PUB_S_SRP " host " PRI_S_SRP
+                     " (instance %p sdref %p)",
+                     instance->instance_name, instance->service_type, host->registered_name,
+                     instance, instance->txn->sdref);
+                return;
+            }
+            INFO("instance registration is stale: " PUB_S_SRP "." PUB_S_SRP " host " PRI_S_SRP
+                 " (instance %p sdref %p)",
+                 instance->instance_name, instance->service_type, host->registered_name,
+                 instance, instance->txn->sdref);
+            instance->txn->sdref = NULL;
+        }
+        ioloop_dnssd_txn_release(instance->txn);
+        instance->txn = NULL;
+    }
+
+    // Get the TSR attribute for the host object.
+    char time_buf[TSR_TIMESTAMP_STRING_LEN];
+    DNSServiceAttributeRef tsr_attribute = srp_message_tsr_attribute_generate(NULL, host->key_id,
+                                                                              time_buf, sizeof(time_buf));
+
+    int err = dns_service_register_wa(server_state, &service_ref,
+                                      (kDNSServiceFlagsShareConnection | kDNSServiceFlagsNoAutoRename |
+                                       kDNSServiceFlagsKnownUnique), server_state->advertise_interface,
+                                      instance->instance_name, instance->service_type, NULL,
+                                      host->registered_name, htons(instance->port), instance->txt_length,
+                                      instance->txt_data, tsr_attribute, service_publisher_instance_callback, instance);
+
+    // This would happen if we pass NULL for regtype, which we don't, or if we run out of memory, or if
+    // the server isn't running; in the second two cases, we can always try again later.
+    if (err != kDNSServiceErr_NoError) {
+        INFO("DNSServiceRegister failed: " PUB_S_SRP "." PUB_S_SRP " host " PRI_S_SRP ": %d (instance %p)",
+             instance->instance_name, instance->service_type, host->registered_name, err, instance);
+    } else {
+        INFO("DNSServiceRegister succeeded: " PUB_S_SRP "." PUB_S_SRP " host " PRI_S_SRP " at " PUB_S_SRP
+             " (instance %p sdref %p)",
+             instance->instance_name, instance->service_type, host->registered_name, time_buf,
+             instance, service_ref);
+        instance->txn = ioloop_dnssd_txn_add_subordinate(service_ref, instance, adv_instance_context_release, NULL);
+        if (instance->txn == NULL) {
+            ERROR("no memory for instance transaction.");
+            DNSServiceRefDeallocate(service_ref);
+            return;
+        }
+        instance->shared_txn = (intptr_t)server_state->shared_registration_txn;
+        adv_instance_retain(instance); // for the callback
+    }
+}
+
+static void
+service_publisher_record_callback(DNSServiceRef UNUSED sdref, DNSRecordRef rref,
+                                  DNSServiceFlags UNUSED flags, DNSServiceErrorType error_code, void *context)
+{
+    adv_record_t *record = context;
+    const char *host_name = record->host != NULL ? record->host->name : "<null>";
+    if (error_code != kDNSServiceErr_NoError) {
+        ERROR("re-registration for " PRI_S_SRP " (record %p rref %p) failed with code %d",
+              host_name, record, rref, error_code);
+        record->rref = NULL;
+        record->shared_txn = 0;
+        adv_record_release(record); // no more callbacks.
+    } else {
+        INFO("re-registration for " PRI_S_SRP " (record %p rref %p) succeeded.", host_name, record, rref);
+        // could get more callbacks.
+    }
+}
+
+static void
+service_publisher_re_advertise_record(srp_server_t *server_state, adv_host_t *host, adv_record_t *record)
+{
+    const DNSServiceRef service_ref = host->server_state->shared_registration_txn->sdref;
+
+    // Make sure we don't double register.
+    if (record->rref != NULL) {
+        if (record->shared_txn == (intptr_t)server_state->shared_registration_txn) {
+            INFO("host is already registered: " PUB_S_SRP " (record %p rref %p)",
+                 host->registered_name, record, record->rref);
+            return;
+        }
+        INFO("host registration is stale: " PUB_S_SRP " (record %p rref %p)",
+             host->registered_name, record, record->rref);
+        srp_mdns_shared_record_remove(host->server_state, record);
+    }
+
+    // Get the TSR attribute for the host object.
+    char time_buf[TSR_TIMESTAMP_STRING_LEN];
+    DNSServiceAttributeRef tsr_attribute = srp_message_tsr_attribute_generate(NULL, host->key_id,
+                                                                              time_buf, sizeof(time_buf));
+
+    int err = dns_service_register_record_wa(server_state, service_ref, &record->rref, kDNSServiceFlagsKnownUnique,
+                                             server_state->advertise_interface, host->registered_name,
+                                             record->rrtype, dns_qclass_in, record->rdlen, record->rdata, ADDRESS_RECORD_TTL,
+                                             tsr_attribute, service_publisher_record_callback, record);
+    if (err != kDNSServiceErr_NoError) {
+        INFO("DNSServiceRegisterRecord failed on host " PUB_S_SRP ": %d (record %p)", host->name, err, record);
+    } else {
+        INFO("DNSServiceRegisterRecord succeeded on host " PUB_S_SRP " at " PUB_S_SRP " (record %p rref %p)",
+             host->name, time_buf, record, record->rref);
+        record->shared_txn = (intptr_t)host->server_state->shared_registration_txn;
+        adv_record_retain(record); // for the callback
+    }
+}
+
+// Re-advertise records that match the address of the WED device we're bonded to if we're bonded to a WED device, or
+// that match the mesh-local prefix of our mesh-local address.  This is a best effort--if it fails, we log it, but it
+// just means that our cached info isn't discoverable and we have to wait for a new registration, which should come soon
+// or else the cached data wasn't valid.
+void
+service_publisher_re_advertise_matching(service_publisher_t *publisher)
+{
+    if (publisher->state_header.state == service_publisher_state_invalid) {
+        INFO("publisher is in an invalid state, so we shouldn't re-advertise anything.");
+        return;
+    }
+
+    srp_server_t *server_state = publisher->server_state;
+
+    // If we don't yet have a shared connection, create one.
+    if (!srp_mdns_shared_registration_txn_setup(server_state)) {
+        return;
+    }
+
+    for (adv_host_t *host = server_state->hosts; host; host = host->next) {
+        bool matched = false;
+
+        if (host->addresses != NULL) {
+            for (int i = 0; i < host->addresses->num; i++) {
+                adv_record_t *record = host->addresses->vec[i];
+                // A match means that if we are in WED p2p mode, the ML-EID of the WED matches the record. If not, then
+                // it means that the record is on the mesh-local prefix. In both cases, the record has to be a valid
+                // AAAA record, of course.
+                if (record != NULL && record->rrtype == dns_rrtype_aaaa && record->rdlen == 16 &&
+                    (publisher->wed_ml_eid_string != NULL
+                     ? !in6addr_compare(&publisher->wed_ml_eid, (struct in6_addr *)record->rdata)
+                     : !in6prefix_compare(&publisher->thread_mesh_local_address, (struct in6_addr *)record->rdata, 8)))
+                {
+                    SEGMENTED_IPv6_ADDR_GEN_SRP(record->rdata, rdata_buf);
+                    INFO("re-advertising " PRI_S_SRP " IN AAAA " PRI_SEGMENTED_IPv6_ADDR_SRP,
+                         host->name, SEGMENTED_IPv6_ADDR_PARAM_SRP(record->rdata, rdata_buf));
+                    service_publisher_re_advertise_record(server_state, host, record);
+                    matched = true;
+                }
+            }
+        }
+        if (matched) {
+            if (host->key_record != NULL) {
+                service_publisher_re_advertise_record(server_state, host, host->key_record);
+            }
+            if (host->instances != NULL) {
+                for (int i = 0; i < host->instances->num; i++) {
+                    adv_instance_t *instance = host->instances->vec[i];
+                    if (instance != NULL && instance->txn == NULL) {
+                        service_publisher_re_advertise_instance(server_state, host, instance);
+                    }
+                }
+            }
+        }
+    }
+
+    publisher->cached_services_published = true;
+}
+
+bool
+service_publisher_is_address_mesh_local(service_publisher_t *publisher, addr_t *address)
+{
+    if (address->sa.sa_family == AF_INET) {
+        IPv4_ADDR_GEN_SRP(&address->sin.sin_addr, addr_buf);
+        if (!IN_LOOPBACK(address->sin.sin_addr.s_addr)) {
+            INFO(PRI_IPv4_ADDR_SRP "is not mesh-local", IPv4_ADDR_PARAM_SRP(&address->sin.sin_addr, addr_buf));
+            return false;
+        }
+        INFO(PRI_IPv4_ADDR_SRP "is the IPv4 loopback address", IPv4_ADDR_PARAM_SRP(&address->sin.sin_addr, addr_buf));
+        return true;
+    }
+    if (address->sa.sa_family != AF_INET6) {
+        INFO("address family %d can't be mesh-local", address->sa.sa_family);
+        return false;
+    }
+
+    uint8_t *addr_ptr = (uint8_t *)&address->sin6.sin6_addr;
+    SEGMENTED_IPv6_ADDR_GEN_SRP(addr_ptr, addr_buf);
+    if (IN6_IS_ADDR_LOOPBACK(&address->sin6.sin6_addr)) {
+        INFO(PRI_SEGMENTED_IPv6_ADDR_SRP " is the IPv6 loopback address.",
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(addr_ptr, addr_buf));
+        return true;
+    }
+    static const uint8_t ipv4mapped_loopback[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 127, 0, 0, 1 };
+    if (!memcmp(&address->sin6.sin6_addr, ipv4mapped_loopback, sizeof(ipv4mapped_loopback))) {
+        INFO(PRI_SEGMENTED_IPv6_ADDR_SRP " is the IPv4-mapped loopback address.",
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(addr_ptr, addr_buf));
+        return true;
+    }
+
+    if (!publisher->have_ml_eid) {
+        INFO(PRI_SEGMENTED_IPv6_ADDR_SRP "is not mesh-local",
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(addr_ptr, addr_buf));
+        return false;
+    }
+
+    SEGMENTED_IPv6_ADDR_GEN_SRP(&publisher->thread_mesh_local_address, mle_buf);
+    if (in6prefix_compare(&address->sin6.sin6_addr, &publisher->thread_mesh_local_address, 8)) {
+        INFO(PRI_SEGMENTED_IPv6_ADDR_SRP
+             " is not on the same prefix as mesh-local address " PRI_SEGMENTED_IPv6_ADDR_SRP ".",
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(addr_ptr, addr_buf),
+             SEGMENTED_IPv6_ADDR_PARAM_SRP(&publisher->thread_mesh_local_address, mle_buf));
+        return false;
+    }
+    INFO(PRI_SEGMENTED_IPv6_ADDR_SRP "is on the same prefix as mesh-local address " PRI_SEGMENTED_IPv6_ADDR_SRP ".",
+         SEGMENTED_IPv6_ADDR_PARAM_SRP(addr_ptr, addr_buf),
+         SEGMENTED_IPv6_ADDR_PARAM_SRP(&publisher->thread_mesh_local_address, mle_buf));
+    return true;
+}
+
 static void
 service_publisher_finalize(service_publisher_t *publisher)
 {
@@ -140,6 +445,10 @@
     thread_service_release(publisher->published_anycast_service);
     thread_service_list_release(&publisher->publication_queue);
 
+    free(publisher->state_header.name);
+    free(publisher->wed_ext_address_string);
+    free(publisher->wed_ml_eid_string);
+    free(publisher->neighbor_ml_eid_string);
     free(publisher->id);
     ioloop_wakeup_release(publisher->wakeup_timer);
     free(publisher);
@@ -201,6 +510,8 @@
     uint8_t server_data[20];
     size_t service_data_length, server_data_length;
     switch(service->service_type) {
+    default:
+        return kCTIStatus_Invalid;
     case unicast_service:
         service_data[0] = THREAD_SRP_SERVER_OPTION;
         service_data_length = 1;
@@ -295,12 +606,28 @@
     for (ppref = &publisher->publication_queue; *ppref != NULL; ppref = &(*ppref)->next)
         ;
     *ppref = service;
-    // Retained the service on the queue. When adding a service we also retain the
-    // service as publisher->published_service, so that retain is always explicit and this retain is always implicit.
+    // Retain the service on the queue.
     thread_service_retain(*ppref);
     service_publisher_queue_run(publisher);
 }
 
+static thread_service_t *
+service_publisher_create_service_for_queue(thread_service_t *service)
+{
+    switch(service->service_type) {
+    case unicast_service:
+        return thread_service_unicast_create(service->rloc16, service->u.unicast.address.s6_addr,
+                                             service->u.unicast.port, service->service_id);
+    case anycast_service:
+        return thread_service_anycast_create(service->rloc16, service->u.anycast.sequence_number, service->service_id);
+    case pref_id:
+        return thread_service_pref_id_create(service->rloc16, service->u.pref_id.partition_id,
+                                             service->u.pref_id.prefix, service->service_id);
+    default:
+        return NULL;
+    }
+}
+
 static void UNUSED
 service_publisher_service_publish(service_publisher_t *publisher, thread_service_t *service)
 {
@@ -308,7 +635,7 @@
 }
 
 static void UNUSED
-service_publisher_service_unpublish(service_publisher_t *publisher, thread_service_type_t service_type)
+service_publisher_service_unpublish(service_publisher_t *publisher, thread_service_type_t service_type, bool enqueue)
 {
     thread_service_t *service;
 
@@ -326,41 +653,56 @@
         ERROR("request to unpublished service that's not present");
         return;
     }
-    service_publisher_queue_update(publisher, service, want_delete);
-    thread_service_release(service);
+
+    if (enqueue) {
+        thread_service_t *to_delete = service_publisher_create_service_for_queue(service);
+        service_publisher_queue_update(publisher, to_delete, want_delete);
+        thread_service_release(to_delete); // service_publisher_queue_update explicitly retains the references it makes.
+        thread_service_release(service); // No longer published.
+    }
 }
 
 static void
 service_publisher_unpublish_stale_service(service_publisher_t *publisher, thread_service_t *service)
 {
-    thread_service_t *to_delete;
+    // If there's a stale service, don't try to publish the real service until we see the stale service go away.
+    INFO("setting seen_service_list to false");
+    publisher->seen_service_list = false;
 
-    switch(service->service_type) {
-    case unicast_service:
-        to_delete = thread_service_unicast_create(service->rloc16,
-                                                  service->u.unicast.address.s6_addr,
-                                                  service->u.unicast.port, service->service_id);
-        break;
-    case anycast_service:
-        to_delete = thread_service_anycast_create(service->rloc16,
-                                                  service->u.anycast.sequence_number, service->service_id);
-        break;
-    case pref_id:
-        to_delete = thread_service_pref_id_create(service->rloc16,
-                                                  service->u.pref_id.partition_id,
-                                                  service->u.pref_id.prefix, service->service_id);
-        break;
-    }
+    thread_service_t *to_delete = service_publisher_create_service_for_queue(service);
+
     if (to_delete == NULL) {
         thread_service_note(publisher->id, service, "no memory for service to delete");
     } else {
         service_publisher_queue_update(publisher, to_delete, want_delete);
+        thread_service_release(to_delete); // service_publisher_queue_update explicitly retains all the references it makes
         service->ignore = true;
     }
 }
 
+static void
+service_publisher_wait_expired(void *context)
+{
+    service_publisher_t *publisher = context;
+    state_machine_event_t *event = state_machine_event_create(state_machine_event_type_timeout, NULL);
+    if (event == NULL) {
+        ERROR("unable to allocate event to deliver");
+        return;
+    }
+    state_machine_event_deliver(&publisher->state_header, event);
+    RELEASE_HERE(event, state_machine_event);
+}
+
+static void
+service_publisher_start_wait(service_publisher_t *publisher, int32_t milliseconds)
+{
+    ioloop_add_wake_event(publisher->wakeup_timer, publisher, service_publisher_wait_expired,
+                          service_publisher_context_release, milliseconds);
+    RETAIN_HERE(publisher, service_publisher); // For wakeup
+}
+
 static bool
-service_publisher_have_competing_unicast_service(service_publisher_t *publisher)
+service_publisher_have_competing_unicast_service(service_publisher_t *publisher, bool want_stale_service_timeout)
 {
     thread_service_t *NULLABLE published_service = publisher->published_unicast_service;
     bool competing_service_present = false;
@@ -380,6 +722,14 @@
                     thread_service_note(publisher->id, service,
                                         "is on our ml-eid or rloc16 but we aren't publishing it, so it's stale.");
                     service_publisher_unpublish_stale_service(publisher, service);
+
+                    if (want_stale_service_timeout &&
+                        !publisher->cached_services_published && !publisher->started_stale_service_timeout)
+                    {
+                        INFO("starting wakeup timer to publish cached services after stale service timeout.");
+                        publisher->started_stale_service_timeout = true;
+                        service_publisher_start_wait(publisher, 2000);
+                    }
                     continue;
                 }
                 thread_service_note(publisher->id, service, "is not ours and we aren't publishing.");
@@ -444,11 +794,10 @@
     return anycast_service_present;
 }
 
-static void
-service_publisher_wait_expired(void *context)
+void
+service_publisher_wanted_service_added(service_publisher_t *publisher)
 {
-    service_publisher_t *publisher = context;
-    state_machine_event_t *event = state_machine_event_create(state_machine_event_type_timeout, NULL);
+    state_machine_event_t *event = state_machine_event_create(state_machine_event_type_srp_needed, NULL);
     if (event == NULL) {
         ERROR("unable to allocate event to deliver");
         return;
@@ -458,6 +807,28 @@
 }
 
 static void
+service_publisher_sed_timeout_expired(void *context)
+{
+    service_publisher_t *publisher = context;
+
+    if (publisher->sed_timeout != NULL) {
+        ioloop_wakeup_release(publisher->sed_timeout);
+        publisher->sed_timeout = NULL;
+    }
+    if (publisher->neighbor_ml_eid_string == NULL) {
+        publisher->neighbor_ml_eid_string = strdup("none");
+        memset(&publisher->neighbor_ml_eid, 0, sizeof(publisher->neighbor_ml_eid));
+    }
+    state_machine_event_t *event = state_machine_event_create(state_machine_event_type_neighbor_ml_eid_changed, NULL);
+    if (event == NULL) {
+        ERROR("unable to allocate event to deliver");
+    } else {
+        state_machine_event_deliver(&publisher->state_header, event);
+        RELEASE_HERE(event, state_machine_event);
+    }
+}
+
+static void
 service_publisher_service_tracker_callback(void *context)
 {
     service_publisher_t *publisher = context;
@@ -465,6 +836,7 @@
     // If we get a service list update when we're associated, set the seen_service-list flag to true. This tells us we can proceed
     // with publishing a service if we just associated to the thread network.
     if (thread_tracker_associated_get(publisher->server_state->thread_tracker, false)) {
+        INFO("setting seen_service_list to true");
         publisher->seen_service_list = true;
     }
 
@@ -536,7 +908,10 @@
 //      on entry: start listeners
 //      listener ready: -> <publishing>
 // <publishing>
-//      on entry: advertise service
+//      on entry: publish the service
+//                start wait timer
+//      on timery expiry: publish the service again
+//      our service shows up: stop the timer
 //      winning service shows up: stop advertising
 //                                -> <not publishing>
 
@@ -557,14 +932,14 @@
     { SERVICE_PUB_NAME_DECL(startup),                            service_publisher_action_startup },
     { SERVICE_PUB_NAME_DECL(waiting_to_publish),                 service_publisher_action_waiting_to_publish },
     { SERVICE_PUB_NAME_DECL(not_publishing),                     service_publisher_action_not_publishing },
-    { SERVICE_PUB_NAME_DECL(start_listeners),                     service_publisher_action_start_listeners },
+    { SERVICE_PUB_NAME_DECL(start_listeners),                    service_publisher_action_start_listeners },
     { SERVICE_PUB_NAME_DECL(publishing),                         service_publisher_action_publishing },
 };
 #define SERVICE_PUBLISHER_NUM_STATES ((sizeof(service_publisher_states)) / (sizeof(state_machine_decl_t)))
 
 #define STATE_MACHINE_HEADER_TO_PUBLISHER(state_header)                                                                \
     if (state_header->state_machine_type != state_machine_type_service_publisher) {                                    \
-        ERROR("state header type isn't omr_publisher: %d", state_header->state_machine_type);                          \
+        ERROR("state header type isn't service_publisher: %d", state_header->state_machine_type);                      \
         return service_publisher_state_invalid;                                                                        \
     }                                                                                                                  \
     service_publisher_t *publisher = state_header->state_object
@@ -578,22 +953,15 @@
     BR_STATE_ANNOUNCE(publisher, event);
 
     if (event == NULL) {
-        if (publisher->wakeup_timer == NULL) {
-            publisher->wakeup_timer = ioloop_wakeup_create();
-        }
-        if (publisher->wakeup_timer == NULL) {
-            ERROR("unable to allocate a wakeup timer");
-            return service_publisher_state_invalid;
-        }
         // We only need a random startup delay for stub routers, which are generally powered devices that can synchronize
         // on restart after a power failure.
+#if STUB_ROUTER
         if (publisher->server_state->stub_router_enabled) {
-            ioloop_add_wake_event(publisher->wakeup_timer, publisher, service_publisher_wait_expired,
-                                  service_publisher_context_release,
-                                  publisher->startup_delay_range + srp_random16() % publisher->startup_delay_range);
+            service_publisher_start_wait(publisher, publisher->startup_delay_range + srp_random16() % publisher->startup_delay_range);
             RETAIN_HERE(publisher, service_publisher); // For wakeup
             return service_publisher_state_invalid;
         }
+#endif
         return service_publisher_state_waiting_to_publish;
     }
 
@@ -613,12 +981,18 @@
     bool no_anycast_service = true;
     bool no_competing_service = true;
     bool associated = true;
-    bool router = true;
+    bool router = false;
     bool have_ml_eid = true;
+    bool have_thread_interface_name = true;
     bool can_publish = true;
+    bool sleepy_router = false;
+    bool sleepy_end_device = false;
+    bool have_node_type = false;
+    bool have_wed_ml_eid = true;
+    bool have_neighbor_ml_eid = true;
 
     // Check the conditions that prevent publication.
-    if (service_publisher_have_competing_unicast_service(publisher)) {
+    if (service_publisher_have_competing_unicast_service(publisher, false)) {
         no_competing_service = false;
         can_publish = false;
     }
@@ -630,29 +1004,85 @@
     if (!thread_tracker_associated_get(server_state->thread_tracker, false)) {
         associated = false;
         can_publish = false;
+        INFO("setting seen_service_list to false");
         publisher->seen_service_list = false;
     }
 
     thread_node_type_t node_type = node_type_tracker_thread_node_type_get(server_state->node_type_tracker, false);
-    if (node_type != node_type_router && node_type != node_type_leader) {
-        router = false;
+    switch(node_type) {
+    case node_type_router:
+    case node_type_leader:
+        router = true;
+        have_node_type = true;
+        break;
+    case node_type_sleepy_router:
+        sleepy_router = true;
+        have_node_type = true;
+        break;
+    case node_type_unknown:
+        have_node_type = false;
+        can_publish = false;
+        break;
+    case node_type_sleepy_end_device:
+    case node_type_synchronized_sleepy_end_device:
+        sleepy_end_device = true;
+        have_node_type = true;
+        break;
+    default:
+        have_node_type = true;
+        break;
     }
+
     if (!publisher->have_ml_eid) {
         have_ml_eid = false;
         can_publish = false;
     }
+    if (!publisher->have_thread_interface_name) {
+        have_thread_interface_name = false;
+        can_publish = false;
+    }
     if (!publisher->seen_service_list) {
         can_publish = false;
     }
+    if (publisher->stopped) {
+        can_publish = false;
+    }
+    if (publisher->wed_ml_eid_string == NULL) {
+        have_wed_ml_eid = false;
+        if (sleepy_router) {
+            can_publish = false;
+        }
+    }
+    if (publisher->neighbor_ml_eid_string == NULL) {
+        have_neighbor_ml_eid = false;
+        if (sleepy_end_device) {
+            if (publisher->sed_timeout == NULL) {
+                publisher->sed_timeout = ioloop_wakeup_create();
+                if (publisher->sed_timeout != NULL) {
+                    ioloop_add_wake_event(publisher->sed_timeout, publisher, service_publisher_sed_timeout_expired,
+                                          service_publisher_context_release, 500);
+                    RETAIN_HERE(publisher, service_publisher);
+                }
+            }
+            can_publish = false;
+        }
+    }
 
-    INFO(PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP,
+    INFO(PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP PUB_S_SRP,
          can_publish ?                         "can publish" :  "can't publish",
          publisher->seen_service_list ?                   "" : " have not seen service list",
          no_competing_service ?                           "" : " competing service present",
          no_anycast_service ?                             "" : " anycast service present",
          associated ?                                     "" : " not associated ",
          router ?                                         "" : " not a router ",
-         have_ml_eid ?                                    "" : " no ml-eid ");
+         sleepy_router ?                                  "" : " not a sleepy router",
+         sleepy_end_device ?                              "" : " not a sleepy end device",
+         have_node_type ?                                 "" : " don't have node type",
+         have_ml_eid ?                                    "" : " no ml-eid ",
+         have_wed_ml_eid ?                                "" : " no wed ml-eid ",
+         have_neighbor_ml_eid ?                           "" : " no neighbor ml-eid ",
+         have_thread_interface_name ?                     "" : " no thread interface name ",
+         publisher->stopped ?                     " stopped" : "");
     return can_publish;
 }
 
@@ -676,6 +1106,19 @@
     return false;
 }
 
+void
+service_publisher_stop_publishing(service_publisher_t *publisher)
+{
+    publisher->stopped = true;
+    state_machine_event_t *event = state_machine_event_create(state_machine_event_type_stop, NULL);
+    if (event == NULL) {
+        ERROR("unable to allocate event to deliver");
+        return;
+    }
+    state_machine_event_deliver(&publisher->state_header, event);
+    RELEASE_HERE(event, state_machine_event);
+}
+
 // We go to this state whenever we think we might need to publish, but are not yet publishing. So this state
 // acts as a gatekeeper: if there is already a service published, we go straight to not_publishing. If we don't
 // yet have an ML-EID, we have to wait until we get one to publish. If, while we are waiting for the ML-EID,
@@ -689,11 +1132,19 @@
 
     // We do the same thing here whether we've gotten an event or just on entry, so no need to check.
     if (service_publisher_can_publish(publisher)) {
+        ioloop_cancel_wake_event(publisher->wakeup_timer);
+        publisher->started_stale_service_timeout = false;
         return service_publisher_state_start_listeners;
     }
-    if (service_publisher_have_competing_unicast_service(publisher)) {
+    if (service_publisher_have_competing_unicast_service(publisher, true)) {
+        ioloop_cancel_wake_event(publisher->wakeup_timer);
+        publisher->started_stale_service_timeout = false;
         return service_publisher_state_not_publishing;
     }
+    // If we saw a stale service, we'll get a timeout event here.
+    if (event != NULL && event->type == state_machine_event_type_timeout && !publisher->cached_services_published) {
+        service_publisher_re_advertise_matching(publisher);
+    }
     return service_publisher_state_invalid;
 }
 
@@ -707,7 +1158,7 @@
 
     if (event == NULL) {
         if (publisher->published_unicast_service != NULL) {
-            service_publisher_service_unpublish(publisher, unicast_service);
+            service_publisher_service_unpublish(publisher, unicast_service, true);
         }
         return service_publisher_state_invalid;
     }
@@ -721,17 +1172,19 @@
 }
 
 static void
-service_publisher_listener_cancel_callback(void *context)
+service_publisher_listener_cancel_callback(comm_t *UNUSED listener, void *context)
 {
     srp_server_t *server_state = context;
     service_publisher_t *publisher = server_state->service_publisher;
-    state_machine_event_t *event = state_machine_event_create(state_machine_event_type_listener_canceled, NULL);
-    if (event == NULL) {
-        ERROR("unable to allocate event to deliver");
-        return;
+    if (publisher != NULL) {
+        state_machine_event_t *event = state_machine_event_create(state_machine_event_type_listener_canceled, NULL);
+        if (event == NULL) {
+            ERROR("unable to allocate event to deliver");
+            return;
+        }
+        state_machine_event_deliver(&publisher->state_header, event);
+        RELEASE_HERE(event, state_machine_event);
     }
-    state_machine_event_deliver(&publisher->state_header, event);
-    RELEASE_HERE(event, state_machine_event);
 }
 
 static void
@@ -742,6 +1195,8 @@
         ioloop_comm_release(publisher->srp_listener);
         publisher->srp_listener = NULL;
     }
+    publisher->have_srp_listener = false;
+    service_publisher_unadvertise_all(publisher);
 }
 
 static void
@@ -749,15 +1204,17 @@
 {
     srp_server_t *server_state = context;
     service_publisher_t *publisher = server_state->service_publisher;
-    state_machine_event_t *event = state_machine_event_create(state_machine_event_type_listener_ready, NULL);
-    if (event == NULL) {
-        ERROR("unable to allocate event to deliver");
-        return;
+    if (publisher != NULL) {
+        state_machine_event_t *event = state_machine_event_create(state_machine_event_type_listener_ready, NULL);
+        if (event == NULL) {
+            ERROR("unable to allocate event to deliver");
+            return;
+        }
+        publisher->have_srp_listener = true;
+        publisher->srp_listener_port = port;
+        state_machine_event_deliver(&publisher->state_header, event);
+        RELEASE_HERE(event, state_machine_event);
     }
-    publisher->have_srp_listener = true;
-    publisher->srp_listener_port = port;
-    state_machine_event_deliver(&publisher->state_header, event);
-    RELEASE_HERE(event, state_machine_event);
 }
 
 static void
@@ -767,11 +1224,13 @@
         FAULT("listener still present");
         service_publisher_listener_cancel(publisher);
     }
-    publisher->srp_listener = srp_proxy_listen(NULL, 0, service_publisher_listener_ready,
-                                               service_publisher_listener_cancel_callback, publisher->server_state);
+    publisher->srp_listener = srp_proxy_listen(NULL, 0, publisher->thread_interface_name, service_publisher_listener_ready,
+                                               service_publisher_listener_cancel_callback, NULL,
+                                               NULL, publisher->server_state);
     if (publisher->srp_listener == NULL) {
         ERROR("failed to setup SRP listener");
     }
+    service_publisher_re_advertise_matching(publisher);
 }
 
 // We go to this state when we have decided to publish, but perhaps do not currently have an SRP listener
@@ -784,7 +1243,11 @@
 
     if (event == NULL) {
         if (publisher->have_srp_listener) {
-            return service_publisher_state_publishing;
+            if (publisher->srp_listener != NULL) {
+                return service_publisher_state_publishing;
+            }
+            FAULT("have_srp_listener is true but there's no listener!");
+            publisher->have_srp_listener = false;
         }
         service_publisher_listener_start(publisher);
         return service_publisher_state_invalid;
@@ -805,6 +1268,73 @@
     return service_publisher_state_invalid;
 }
 
+static state_machine_state_t
+service_publisher_published_services_seen(service_publisher_t *publisher)
+{
+    return ((publisher->published_unicast_service == NULL || publisher->have_unicast_in_net_data) &&
+            (publisher->published_anycast_service == NULL || publisher->have_anycast_in_net_data));
+}
+
+static bool
+service_publisher_wanted_service_missing(service_publisher_t *publisher)
+{
+    srp_server_t *server_state = publisher->server_state;
+
+        // If we get here, the named service instance is not represented in the current set of services
+        // about which we have information.
+#if STUB_ROUTER
+    if (server_state->stub_router_enabled) {
+        return true; // always publish when stub router
+    }
+#endif
+    if (server_state->srp_service_needed) {
+        INFO("srp_service_needed == true -> true");
+        return true; // srp service unconditionally requested
+    }
+
+    for (wanted_service_t *service = server_state->wanted_services; service != NULL; service = service->next) {
+        for (adv_host_t *host = server_state->hosts; host != NULL; host = host->next) {
+            if (host->instances != NULL) {
+                for (int i = 0; i < host->instances->num; i++) {
+                    adv_instance_t *instance = host->instances->vec[i];
+                    if (instance != NULL) {
+                        if (!strcasecmp(instance->instance_name, service->name) &&
+                            !adv_ctl_service_types_compare(service->service_type, instance->service_type))
+                        {
+                            if (host->addresses != NULL) {
+                                for (int j = 0; j < host->addresses->num; j++) {
+                                    adv_record_t *address = host->addresses->vec[j];
+                                    if (address != NULL) {
+                                        if (address->rdlen == 16 &&
+                                            !in6prefix_compare((struct in6_addr *)address->rdata,
+                                                               &publisher->thread_mesh_local_address, 8))
+                                        {
+                                            goto instance_found;
+                                        }
+                                    }
+                                }
+                                INFO("srp service " PRI_S_SRP "." PRI_S_SRP " present as " PRI_S_SRP
+                                     " but has no address on local mesh -> true",
+                                     service->name, service->service_type, instance->instance_name);
+                                return true; // There is no valid address for this instance in the cache.
+                            }
+                            INFO("srp service " PRI_S_SRP "." PRI_S_SRP " present but no addresses -> true",
+                                 service->name, service->service_type);
+                            return true; // We didn't find the named instance in the database
+                        }
+                    }
+                }
+            }
+        }
+        INFO("service " PRI_S_SRP "." PRI_S_SRP " host not present -> true", service->name, service->service_type);
+        return true;
+    instance_found:
+        INFO("service " PRI_S_SRP "." PRI_S_SRP " is present", service->name, service->service_type);
+    }
+    INFO("all needed services present -> false");
+    return false; // There weren't any named instances for which we don't have a usable registration.
+}
+
 // We enter this state when we have an SRP listener and no competing unicast services. On entry, we publish our unicast service.
 // If a competing service shows up that wins, we stop publishing and cancel the listener. Otherwise we remain in this state.
 static state_machine_state_t
@@ -813,48 +1343,124 @@
     STATE_MACHINE_HEADER_TO_PUBLISHER(state_header);
     BR_STATE_ANNOUNCE(publisher, event);
 
-    if (event == NULL) {
+    if (event == NULL || event->type == state_machine_event_type_timeout ||
+        event->type == state_machine_event_type_srp_needed)
+    {
         if (publisher->published_unicast_service != NULL) {
-            ERROR("unicast service still published!");
-            service_publisher_service_unpublish(publisher, unicast_service);
+            // We shouldn't see a published service on state entry.
+            if (event == NULL) {
+                ERROR("unicast service still published!");
+            }
+            // Only actually enqueue a delete if we aren't retrying.
+            service_publisher_service_unpublish(publisher, unicast_service, event == NULL);
         }
-        uint8_t port[] = { publisher->srp_listener_port >> 8, publisher->srp_listener_port & 255 };
-        thread_service_t *service = thread_service_unicast_create(publisher->server_state->rloc16,
-                                                                  (uint8_t *)&publisher->thread_mesh_local_address,
-                                                                  port, 0);
 
-        service_publisher_service_publish(publisher, service);
-        thread_service_release(service); // service_publisher_publish retains the references it keeps.
+        // On non-BR devices, don't actually publish the service until we get a signal that it's needed.
+        if (!publisher->server_state->srp_on_demand || service_publisher_wanted_service_missing(publisher)) {
+            uint8_t port[] = { publisher->srp_listener_port >> 8, publisher->srp_listener_port & 255 };
+            thread_service_t *service = thread_service_unicast_create(publisher->server_state->rloc16,
+                                                                      (uint8_t *)&publisher->thread_mesh_local_address,
+                                                                      port, 0);
+            service_publisher_service_publish(publisher, service);
+            thread_service_release(service); // service_publisher_publish retains the references it keeps.
+
+            // Set up a retransmit timer in case the service publication fails.
+            if (event == NULL) {
+                publisher->retry_interval = 5; // First retry after five seconds
+            } else {
+                // Maybe the service tracker is wedged, so restart it
+                service_tracker_start(publisher->server_state->service_tracker);
+
+                // Exponential backoff.
+                if (publisher->retry_interval < 3600) {
+                    publisher->retry_interval *= 2;
+                }
+            }
+            service_publisher_start_wait(publisher, (publisher->retry_interval * MSEC_PER_SEC +
+                                                     srp_random32() % (publisher->retry_interval * MSEC_PER_SEC) / 2));
+        }
+
         return service_publisher_state_invalid;
     }
 
     // If the listener got canceled for some reason, restart it.
     if (event->type == state_machine_event_type_listener_canceled) {
-        service_publisher_service_unpublish(publisher, unicast_service);
+        service_publisher_service_unpublish(publisher, unicast_service, true);
         publisher->startup_delay_range = SERVICE_PUBLISHER_LISTENER_RESTART_WAIT;
         return service_publisher_state_startup;
     }
 
     // Any other event triggers a re-evaluation.
-    if (event->type == state_machine_event_type_ml_eid_changed ||
-        !service_publisher_can_publish(publisher))
-    {
+    if (event->type == state_machine_event_type_ml_eid_changed) {
         service_publisher_listener_cancel(publisher);
-        service_publisher_service_unpublish(publisher, unicast_service);
+        service_publisher_service_unpublish(publisher, unicast_service, true);
+        return service_publisher_state_startup;
+    }
+
+    if (!service_publisher_can_publish(publisher)) {
+        service_publisher_listener_cancel(publisher);
+        service_publisher_service_unpublish(publisher, unicast_service, true);
         return service_publisher_state_not_publishing;
     }
 
+    // If we haven't yet seen all the services we are publishing in the network data, check to see if it showed up.
+    if (event->type == state_machine_event_type_service_list_changed &&
+        !service_publisher_published_services_seen(publisher))
+    {
+        publisher->have_unicast_in_net_data = false;
+        publisher->have_anycast_in_net_data = false;
+        for (thread_service_t *service = service_tracker_services_get(publisher->server_state->service_tracker);
+             service != NULL; service = service->next)
+        {
+            if (service->service_type == unicast_service && service->ncp &&
+                publisher->published_unicast_service != NULL &&
+                !in6addr_compare(&service->u.unicast.address, &publisher->published_unicast_service->u.unicast.address) &&
+                !memcmp(service->u.unicast.port, publisher->published_unicast_service->u.unicast.port, 2))
+            {
+                publisher->have_unicast_in_net_data = true;
+            }
+            else if (service->service_type == anycast_service && service->ncp &&
+                       publisher->server_state->have_rloc16 && service->rloc16 == publisher->server_state->rloc16 &&
+                       publisher->published_anycast_service != NULL &&
+                       service->u.anycast.sequence_number == publisher->published_anycast_service->u.anycast.sequence_number)
+            {
+                publisher->have_anycast_in_net_data = true;
+            }
+        }
+
+        // If all of the services we are publishing are showing up in NCP, we can cancel the timer and go to the publishing state.
+        if (service_publisher_published_services_seen(publisher)) {
+            ioloop_cancel_wake_event(publisher->wakeup_timer);
+            return service_publisher_state_invalid;
+        }
+    }
     return service_publisher_state_invalid;
 }
 
 void
 service_publisher_cancel(service_publisher_t *publisher)
 {
+    ioloop_cancel_wake_event(publisher->wakeup_timer);
+    service_publisher_listener_cancel(publisher);
     service_tracker_callback_cancel(publisher->server_state->service_tracker, publisher);
     thread_tracker_callback_cancel(publisher->server_state->thread_tracker, publisher);
     node_type_tracker_callback_cancel(publisher->server_state->node_type_tracker, publisher);
-    cti_events_discontinue(publisher->ml_eid_connection);
-    publisher->ml_eid_connection = NULL;
+    if (publisher->active_data_set_connection != NULL) {
+        cti_events_discontinue(publisher->active_data_set_connection);
+        publisher->active_data_set_connection = NULL;
+        RELEASE_HERE(publisher, service_publisher);
+    }
+    if (publisher->wed_tracker_connection != NULL) {
+        cti_events_discontinue(publisher->wed_tracker_connection);
+        publisher->wed_tracker_connection = NULL;
+        RELEASE_HERE(publisher, service_publisher);
+    }
+    if (publisher->neighbor_tracker_connection != NULL) {
+        cti_events_discontinue(publisher->neighbor_tracker_connection);
+        publisher->neighbor_tracker_connection = NULL;
+        RELEASE_HERE(publisher, service_publisher);
+    }
+    state_machine_cancel(&publisher->state_header);
 }
 
 service_publisher_t *
@@ -953,6 +1559,27 @@
     publisher->thread_mesh_local_address = new_mesh_local_address;
     publisher->have_ml_eid = true;
 
+    for (thread_service_t *service = service_tracker_services_get(publisher->server_state->service_tracker);
+         service != NULL; service = service->next)
+    {
+        if (service->ignore) {
+            continue;
+        }
+        if (service->service_type == unicast_service) {
+            if (publisher->published_unicast_service == NULL) {
+                if (service->rloc16 == publisher->server_state->rloc16 ||
+                    (publisher->have_ml_eid &&
+                     !in6addr_compare(&service->u.unicast.address, &publisher->thread_mesh_local_address)))
+                {
+                    thread_service_note(publisher->id, service,
+                                        "is on our ml-eid or rloc16 but we aren't publishing it, so it's stale.");
+                    service_publisher_unpublish_stale_service(publisher, service);
+                    continue;
+                }
+            }
+        }
+    }
+
     state_machine_event_t *event = state_machine_event_create(state_machine_event_type_ml_eid_changed, NULL);
     if (event == NULL) {
         ERROR("unable to allocate event to deliver");
@@ -960,24 +1587,260 @@
     }
     state_machine_event_deliver(&publisher->state_header, event);
     RELEASE_HERE(event, state_machine_event);
+    RELEASE_HERE(publisher, service_publisher); // callback held a reference.
     return;
 fail:
+    RELEASE_HERE(publisher, service_publisher); // callback held a reference.
     publisher->have_ml_eid = false;
     return;
 }
 
+static void
+service_publisher_active_data_set_changed_callback(void *context, cti_status_t status)
+{
+    service_publisher_t *publisher = context;
+
+    if (status != kCTIStatus_NoError) {
+        ERROR("error %d", status);
+        RELEASE_HERE(publisher, service_publisher); // no more callbacks
+        cti_events_discontinue(publisher->active_data_set_connection);
+        publisher->active_data_set_connection = NULL;
+        return;
+    }
+
+    status = cti_get_mesh_local_address(publisher->server_state, publisher,
+                                        service_publisher_get_mesh_local_address_callback, NULL);
+    if (status != kCTIStatus_NoError) {
+        ERROR("cti_get_mesh_local_address failed with status %d", status);
+    } else {
+        RETAIN_HERE(publisher, service_publisher); // for mesh-local callback
+    }
+}
+
+static void
+service_publisher_tunnel_name_callback(void *context, const char *name, cti_status_t status)
+{
+    service_publisher_t *publisher = context;
+    if (status == kCTIStatus_Disconnected || status == kCTIStatus_DaemonNotRunning) {
+        INFO("disconnected");
+        goto out;
+    }
+
+    if (status != kCTIStatus_NoError) {
+        INFO(PUB_S_SRP " %d", name != NULL ? name : "<null>", status);
+        goto out;
+    }
+    publisher->have_thread_interface_name = true;
+
+    // Get rid of the old interface name if it's changed.
+    bool changed = true;
+    if (publisher->thread_interface_name != NULL) {
+        if (!strcmp(name, publisher->thread_interface_name)) {
+            changed = false;
+        } else {
+            free(publisher->thread_interface_name);
+            publisher->thread_interface_name = NULL;
+        }
+    }
+
+    // Store the new interface name if it's changed.
+    if (changed) {
+        publisher->thread_interface_name = strdup(name);
+
+        INFO("thread interface at " PUB_S_SRP, name);
+    }
+
+    if (publisher->thread_interface_name == NULL) {
+        ERROR("No memory to save thread interface name " PUB_S_SRP, name);
+    }
+
+    state_machine_event_t *event = state_machine_event_create(state_machine_event_type_thread_interface_changed, NULL);
+    if (event == NULL) {
+        ERROR("unable to allocate event to deliver");
+        goto out;
+    }
+    state_machine_event_deliver(&publisher->state_header, event);
+    RELEASE_HERE(event, state_machine_event);
+out:
+    RELEASE_HERE(publisher, service_publisher); // callback held a reference.
+}
+
+static void
+service_publisher_wed_callback(void *context, const char *ext_address, const char *ml_eid, bool added, int status)
+{
+    service_publisher_t *publisher = context;
+    if (status == kCTIStatus_Disconnected || status == kCTIStatus_DaemonNotRunning) {
+        INFO("disconnected");
+        goto out;
+    }
+
+    const char *none = "<none>";
+    const char *ea = none;
+    if (ext_address != NULL) {
+        ea = ext_address;
+    }
+    const char *mle = none;
+    if (ml_eid != NULL) {
+        int ret = inet_pton(AF_INET6, ml_eid, &publisher->wed_ml_eid);
+        if (ret) {
+            mle = ml_eid;
+        }
+    }
+
+    INFO("ext_address: " PRI_S_SRP "  ml_eid: " PRI_S_SRP PUB_S_SRP " %d", ea, mle, added ? " added" : " removed", status);
+    if (status != kCTIStatus_NoError) {
+        goto out;
+    }
+
+    if (publisher->wed_ext_address_string != NULL) {
+        free(publisher->wed_ext_address_string);
+        publisher->wed_ext_address_string = NULL;
+    }
+
+    if (publisher->wed_ml_eid_string != NULL) {
+        free(publisher->wed_ml_eid_string);
+        publisher->wed_ml_eid_string = NULL;
+    }
+
+    // API guarantees addresses are non-NULL if added is true.
+    if (added) {
+        if (ea != none) {
+            publisher->wed_ext_address_string = strdup(ea);
+            if (publisher->wed_ext_address_string == NULL) {
+                ERROR("no memory for wed_ext_address string!");
+            }
+        }
+        if (mle != none) {
+            publisher->wed_ml_eid_string = strdup(mle);
+            if (publisher->wed_ml_eid_string == NULL) {
+                ERROR("no memory for wed_ml_eid string!");
+                memset(&publisher->wed_ml_eid, 0, sizeof(publisher->wed_ml_eid));
+            }
+        } else {
+            memset(&publisher->wed_ml_eid, 0, sizeof(publisher->wed_ml_eid));
+        }
+    }
+    state_machine_event_t *event = state_machine_event_create(state_machine_event_type_wed_ml_eid_changed, NULL);
+    if (event == NULL) {
+        ERROR("unable to allocate event to deliver");
+        goto out;
+    }
+    state_machine_event_deliver(&publisher->state_header, event);
+    RELEASE_HERE(event, state_machine_event);
+out:
+    ;
+}
+
+static void
+service_publisher_neighbor_callback(void *context, const char *ml_eid, cti_status_t status)
+{
+    service_publisher_t *publisher = context;
+    if (status == kCTIStatus_Disconnected || status == kCTIStatus_DaemonNotRunning) {
+        INFO("disconnected");
+        goto out;
+    }
+
+    const char *none = "<none>";
+    const char *mle = none;
+    if (ml_eid != NULL) {
+        if (!strcmp(ml_eid, "none")) {
+            mle = ml_eid;
+            memset(&publisher->neighbor_ml_eid, 0, sizeof(publisher->neighbor_ml_eid));
+        } else {
+            int ret = inet_pton(AF_INET6, ml_eid, &publisher->neighbor_ml_eid);
+            if (ret) {
+                mle = ml_eid;
+            }
+        }
+    }
+
+    INFO("ml_eid: " PRI_S_SRP ", status %d", mle, status);
+    if (status != kCTIStatus_NoError) {
+        goto out;
+    }
+
+
+    if (publisher->neighbor_ml_eid_string != NULL) {
+        free(publisher->neighbor_ml_eid_string);
+        publisher->neighbor_ml_eid_string = NULL;
+    }
+
+    if (mle != none) {
+        publisher->neighbor_ml_eid_string = strdup(mle);
+        if (publisher->neighbor_ml_eid_string == NULL) {
+            ERROR("no memory for neighbor_ml_eid string!");
+            memset(&publisher->neighbor_ml_eid, 0, sizeof(publisher->neighbor_ml_eid));
+        }
+    } else {
+        memset(&publisher->neighbor_ml_eid, 0, sizeof(publisher->neighbor_ml_eid));
+    }
+    state_machine_event_t *event = state_machine_event_create(state_machine_event_type_neighbor_ml_eid_changed, NULL);
+    if (event == NULL) {
+        ERROR("unable to allocate event to deliver");
+        goto out;
+    }
+    state_machine_event_deliver(&publisher->state_header, event);
+    RELEASE_HERE(event, state_machine_event);
+
+    if (publisher->sed_timeout != NULL) {
+        ioloop_cancel_wake_event(publisher->sed_timeout);
+        ioloop_wakeup_release(publisher->sed_timeout);
+        publisher->sed_timeout = NULL;
+    }
+out:
+    ;
+}
+
 void
 service_publisher_start(service_publisher_t *publisher)
 {
-    // Get mesh local address
-    cti_status_t status = cti_get_mesh_local_address(publisher->server_state, &publisher->ml_eid_connection, publisher,
-                                                     service_publisher_get_mesh_local_address_callback, NULL);
+    cti_status_t status = cti_track_active_data_set(publisher->server_state, &publisher->active_data_set_connection,
+                                                    publisher, service_publisher_active_data_set_changed_callback,
+                                                    NULL);
     if (status != kCTIStatus_NoError) {
-        ERROR("cti_get_mesh_local_address failed with status %d", status);
+        ERROR("unable to start tracking active dataset: %d", status);
+    } else {
+        RETAIN_HERE(publisher, service_publisher); // for active dataset callback
     }
+
+    status = cti_get_tunnel_name(publisher->server_state, publisher, service_publisher_tunnel_name_callback, NULL);
+    if (status != kCTIStatus_NoError) {
+        ERROR("unable to get tunnel name: %d", status);
+    } else {
+        RETAIN_HERE(publisher, service_publisher); // for tunnel name callback
+    }
+
+
+    status = cti_track_wed_status(publisher->server_state, &publisher->wed_tracker_connection,
+                                  publisher, service_publisher_wed_callback, NULL);
+    if (status != kCTIStatus_NoError) {
+        FAULT("can't track WED status: %d", status);
+    } else {
+        RETAIN_HERE(publisher, service_publisher);
+    }
+
+    status = cti_track_neighbor_ml_eid(publisher->server_state, &publisher->neighbor_tracker_connection,
+                                       publisher, service_publisher_neighbor_callback, NULL);
+    if (status != kCTIStatus_NoError) {
+        FAULT("can't track WED status: %d", status);
+    } else {
+        RETAIN_HERE(publisher, service_publisher);
+    }
+
+    service_publisher_active_data_set_changed_callback(publisher, kCTIStatus_NoError); // Get the initial state.
     state_machine_next_state(&publisher->state_header, service_publisher_state_startup);
 }
 
+bool
+service_publisher_get_ml_eid(service_publisher_t *publisher, struct in6_addr *ml_eid)
+{
+    if (publisher != NULL && publisher->have_ml_eid) {
+        in6addr_copy(ml_eid, &publisher->thread_mesh_local_address);
+        return true;
+    }
+    return false;
+}
+
 // Local Variables:
 // mode: C
 // tab-width: 4
diff --git a/ServiceRegistration/service-publisher.h b/ServiceRegistration/service-publisher.h
index 1c15675..a87eaa4 100644
--- a/ServiceRegistration/service-publisher.h
+++ b/ServiceRegistration/service-publisher.h
@@ -1,6 +1,6 @@
 /* service-publisher.h
  *
- * Copyright (c) 2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2023-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -26,13 +26,20 @@
 typedef struct service_publisher service_publisher_t;
 
 RELEASE_RETAIN_DECLS(service_publisher);
+#define service_publisher_retain(watcher) service_publisher_retain_(watcher, __FILE__, __LINE__)
+#define service_publisher_release(watcher) service_publisher_release_(watcher, __FILE__, __LINE__)
 
+bool service_publisher_is_address_mesh_local(service_publisher_t *NONNULL publisher, addr_t *NONNULL address);
 bool service_publisher_could_publish(service_publisher_t *NULLABLE publisher);
 void service_publisher_cancel(service_publisher_t *NONNULL publisher);
 service_publisher_t *NULLABLE service_publisher_create(srp_server_t *NONNULL server_state);
 void service_publisher_start(service_publisher_t *NONNULL publisher);
+void service_publisher_stop_publishing(service_publisher_t *NONNULL publisher);
+bool service_publisher_get_ml_eid(service_publisher_t *NULLABLE publisher, struct in6_addr *NONNULL ml_eid);
 
-
+void service_publisher_unadvertise_all(service_publisher_t *NONNULL publisher);
+void service_publisher_re_advertise_matching(service_publisher_t *NONNULL publisher);
+void service_publisher_wanted_service_added(service_publisher_t *NONNULL publisher);
 #endif // _SERVICE_PUBLISHER_H__
 
 // Local Variables:
diff --git a/ServiceRegistration/service-tracker.c b/ServiceRegistration/service-tracker.c
index abc6515..f05b2bb 100644
--- a/ServiceRegistration/service-tracker.c
+++ b/ServiceRegistration/service-tracker.c
@@ -1,6 +1,6 @@
 /* service-tracker.c
  *
- * Copyright (c) 2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2023-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -48,6 +48,7 @@
 #include "thread-service.h"
 #include "service-tracker.h"
 #include "probe-srp.h"
+#include "adv-ctl-server.h"
 
 struct service_tracker_callback {
 	service_tracker_callback_t *next;
@@ -65,6 +66,7 @@
 	service_tracker_callback_t *callbacks;
     thread_service_t *NULLABLE thread_services;
     uint16_t rloc16;
+    bool user_service_seen;
 };
 
 static uint64_t service_tracker_serial_number = 0;
@@ -80,6 +82,15 @@
     free(tracker);
 }
 
+static void
+service_tracker_context_release(void *context)
+{
+    service_tracker_t *tracker = context;
+    if (tracker != NULL) {
+        RELEASE_HERE(tracker, service_tracker);
+    }
+}
+
 RELEASE_RETAIN_FUNCS(service_tracker);
 
 void
@@ -125,6 +136,7 @@
     service_tracker_t *tracker = context;
     size_t i;
     thread_service_t **pservice = &tracker->thread_services, *service = NULL;
+    tracker->user_service_seen = false;
 
     if (status == kCTIStatus_Disconnected || status == kCTIStatus_DaemonNotRunning) {
         INFO("[ST%lld] disconnected", tracker->id);
@@ -259,15 +271,25 @@
                     service->ncp = true;
                 } else {
                     service->user = true;
+                    tracker->user_service_seen = true;
                 }
                 if (cti_service->flags & kCTIFlag_Stable) {
                     service->stable = true;
                 }
             }
         }
-
         accumulator_t accumulator;
         for (service = tracker->thread_services; service != NULL; service = service->next) {
+            // For unicast services, see if there's also an anycast service on the same RLOC16.
+            if (service->service_type == unicast_service) {
+                service->u.unicast.anycast_also_present = false;
+                for (thread_service_t *aservice = tracker->thread_services; aservice != NULL; aservice = aservice->next)
+                {
+                    if (aservice->service_type == anycast_service && aservice->rloc16 == service->rloc16) {
+                        service->u.unicast.anycast_also_present = true;
+                    }
+                }
+            }
             service_tracker_flags_accumulator_init(&accumulator);
             service_tracker_flags_accumulate(&accumulator, service->previous_ncp, service->ncp, "ncp");
             service_tracker_flags_accumulate(&accumulator, service->previous_stable, service->ncp, "stable");
@@ -282,9 +304,21 @@
         for (service_tracker_callback_t *callback = tracker->callbacks; callback != NULL; callback = callback->next) {
             callback->callback(callback->context);
         }
+        if (!tracker->user_service_seen && tracker->server_state != NULL &&
+            tracker->server_state->awaiting_service_removal)
+        {
+            tracker->server_state->awaiting_service_removal = false;
+            adv_ctl_thread_shutdown_status_check(tracker->server_state);
+        }
     }
 }
 
+bool
+service_tracker_local_service_seen(service_tracker_t *tracker)
+{
+    return tracker->user_service_seen;
+}
+
 service_tracker_t *
 service_tracker_create(srp_server_t *server_state)
 {
@@ -310,18 +344,24 @@
 void
 service_tracker_start(service_tracker_t *tracker)
 {
-    if (tracker->thread_service_context == NULL) {
-        int status = cti_get_service_list(tracker->server_state, &tracker->thread_service_context,
-                                          tracker, service_tracker_callback, NULL);
-        if (status != kCTIStatus_NoError) {
-            INFO("[ST%lld] service list get failed: %d", tracker->id, status);
-            return;
+    if (tracker->thread_service_context != NULL) {
+        cti_events_discontinue(tracker->thread_service_context);
+        tracker->thread_service_context = NULL;
+        INFO("[ST%lld] restarting", tracker->id);
+        if (tracker->ref_count != 1) {
+            RELEASE_HERE(tracker, service_tracker); // Release the old retain for the callback.
+        } else {
+            FAULT("service tracker reference count should not be 1 here!");
         }
-        INFO("[ST%lld] service list get started", tracker->id);
-        RETAIN_HERE(tracker, service_tracker); // for the callback.
-    } else {
-        INFO("[ST%lld] already started", tracker->id);
     }
+    int status = cti_get_service_list(tracker->server_state, &tracker->thread_service_context,
+                                      tracker, service_tracker_callback, NULL);
+    if (status != kCTIStatus_NoError) {
+        INFO("[ST%lld] service list get failed: %d", tracker->id, status);
+        return;
+    }
+    INFO("[ST%lld] service list get started", tracker->id);
+    RETAIN_HERE(tracker, service_tracker); // for the callback.
 }
 
 bool
@@ -499,22 +539,33 @@
     return false;
 }
 
-// If true, there is a service on the list that we can still try to verify
+// Check for an unverified service on the list. If we are currently checking a service, return that service.
 thread_service_t *
-service_tracker_unverified_service_get(service_tracker_t *NULLABLE tracker)
+service_tracker_unverified_service_get(service_tracker_t *NULLABLE tracker, thread_service_type_t service_type)
 {
-    if (tracker == NULL) {
-        return NULL;
-    }
-    for (thread_service_t *service = tracker->thread_services; service != NULL; service = service->next) {
-        if (service->ignore) {
-            continue;
-        }
-        if (!service->checked && !service->probe_state && !service->user) {
-            return service;
+    thread_service_t *ret = NULL;
+    if (tracker != NULL) {
+        for (thread_service_t *service = tracker->thread_services; service != NULL; service = service->next) {
+            if (service_type != any_service && service_type != service->service_type) {
+                continue;
+            }
+            if (service->ignore) {
+                continue;
+            }
+            if (service->checking) {
+                return service;
+            }
+            if (ret == NULL && !service->checked && !service->probe_state && !service->user) {
+                ret = service;
+            }
         }
     }
-    return NULL;
+    if (tracker != NULL && ret != NULL) {
+        char buf[128];
+        snprintf(buf, sizeof(buf), "service_tracker_unverified_service_get returning %p", ret);
+        service_tracker_thread_service_note(tracker, ret, buf);
+    }
+    return ret;
 }
 
 static void
@@ -559,11 +610,25 @@
             continue;
         }
         // If we didn't continue, yet, it's because we found a service we can probe, so probe it.
-        probe_srp_service(service, tracker, service_tracker_probe_callback);
+        RETAIN_HERE(tracker, service_tracker); // For the srp probe
+        probe_srp_service(service, tracker, service_tracker_probe_callback, service_tracker_context_release);
         return;
     }
 }
 
+void
+service_tracker_cancel_probes(service_tracker_t *NULLABLE tracker)
+{
+    if (tracker == NULL) {
+        return;
+    }
+    for (thread_service_t *service = tracker->thread_services; service != NULL; service = service->next) {
+        if (service->probe_state) {
+            probe_srp_service_probe_cancel(service);
+        }
+    }
+}
+
 // Local Variables:
 // mode: C
 // tab-width: 4
diff --git a/ServiceRegistration/service-tracker.h b/ServiceRegistration/service-tracker.h
index bca4032..1f96792 100644
--- a/ServiceRegistration/service-tracker.h
+++ b/ServiceRegistration/service-tracker.h
@@ -23,10 +23,14 @@
 
 typedef struct service_tracker_callback service_tracker_callback_t;
 typedef struct service_tracker service_tracker_t;
+typedef struct srp_server_state srp_server_t;
 
 RELEASE_RETAIN_DECLS(service_tracker);
+#define service_tracker_retain(watcher) service_tracker_retain_(watcher, __FILE__, __LINE__)
+#define service_tracker_release(watcher) service_tracker_release_(watcher, __FILE__, __LINE__)
 void service_tracker_stop(service_tracker_t *NONNULL tracker);
 void service_tracker_cancel(service_tracker_t *NONNULL tracker);
+bool service_tracker_local_service_seen(service_tracker_t *NONNULL tracker);
 service_tracker_t *NULLABLE service_tracker_create(srp_server_t *NONNULL route_state);
 void service_tracker_set_reconnect_callback(service_tracker_t *NONNULL tracker,
 										  void (*NULLABLE reconnect_callback)(void *NULLABLE context));
@@ -41,8 +45,10 @@
 bool service_tracker_verified_service_still_exists(service_tracker_t *NULLABLE tracker,
                                                    thread_service_t *NULLABLE old_service);
 thread_service_t *NULLABLE service_tracker_verified_service_get(service_tracker_t *NULLABLE tracker);
-thread_service_t *NULLABLE service_tracker_unverified_service_get(service_tracker_t *NULLABLE tracker);
+thread_service_t *NULLABLE service_tracker_unverified_service_get(service_tracker_t *NULLABLE tracker,
+                                                                  thread_service_type_t service_type);
 void service_tracker_verify_next_service(service_tracker_t *NULLABLE tracker);
+void service_tracker_cancel_probes(service_tracker_t *NULLABLE tracker);
 #endif // __SERVICE_TRACKER_H__
 
 // Local Variables:
diff --git a/ServiceRegistration/sign-macos.c b/ServiceRegistration/sign-macos.c
index 004343a..363fda5 100644
--- a/ServiceRegistration/sign-macos.c
+++ b/ServiceRegistration/sign-macos.c
@@ -100,8 +100,8 @@
 static srp_key_t *
 srp_get_key_internal(const char *key_name, bool delete)
 {
-    long two56 = 256;
     srp_key_t *key = NULL;
+    long two56 = 256;
     OSStatus status;
 
     CFMutableDictionaryRef key_parameters = CFDictionaryCreateMutable(NULL, 8,
@@ -121,6 +121,7 @@
         CFDictionaryAddValue(key_parameters, kSecReturnRef, kCFBooleanTrue);
         CFDictionaryAddValue(key_parameters, kSecMatchLimit, kSecMatchLimitOne);
         CFDictionaryAddValue(key_parameters, kSecClass, kSecClassKey);
+        CFDictionaryAddValue(key_parameters, kSecUseDataProtectionKeychain, kCFBooleanTrue);
         pubkey_parameters = CFDictionaryCreateMutableCopy(NULL, 8, key_parameters);
         if (pubkey_parameters != NULL) {
             CFDictionaryAddValue(key_parameters, kSecAttrKeyClass, kSecAttrKeyClassPrivate);
@@ -169,6 +170,8 @@
             CFRelease(pubkey_parameters);
         }
     }
+    (void)key_name;
+    (void)delete;
     return key;
 }
 
@@ -212,8 +215,8 @@
 size_t
 srp_pubkey_copy(uint8_t *buf, size_t max, srp_key_t *key)
 {
-    CFErrorRef error = NULL;
     size_t ret = 0;
+    CFErrorRef error = NULL;
     CFDataRef pubkey = SecKeyCopyExternalRepresentation(key->public, &error);
     if (pubkey == NULL) {
         if (error != NULL) {
@@ -246,12 +249,12 @@
 srp_sign(uint8_t *output, size_t max, uint8_t *message, size_t msglen,
          uint8_t *rr, size_t rdlen, srp_key_t *key)
 {
+    int ret = 0;
     CFMutableDataRef payload = NULL;
     CFDataRef signature = NULL;
     CFErrorRef error = NULL;
     const uint8_t *bytes;
     unsigned long len;
-    int ret = 0;
 
     if (max < ECDSA_SHA256_SIG_SIZE) {
         ERROR("srp_sign: not enough space in output buffer (%lu) for signature (%d).",
diff --git a/ServiceRegistration/srp-api.h b/ServiceRegistration/srp-api.h
index 6c16692..f1bc3ff 100644
--- a/ServiceRegistration/srp-api.h
+++ b/ServiceRegistration/srp-api.h
@@ -29,9 +29,20 @@
 typedef void (*srp_hostname_conflict_callback_t)(const char *NONNULL hostname);
 typedef void (*srp_wakeup_callback_t)(void *NONNULL state);
 typedef void (*srp_datagram_callback_t)(void *NONNULL state, void *NONNULL message, size_t message_length);
+typedef struct client_state client_state_t;
+typedef struct dns_wire dns_wire_t;
 
 // The below functions provide a way for the host to inform the SRP service of the state of the network.
 
+// For testing
+client_state_t *NULLABLE srp_client_get_current(void);
+void srp_client_set_current(client_state_t *NONNULL new_client);
+dns_wire_t *NULLABLE srp_client_generate_update(client_state_t *NONNULL client,
+                                                uint32_t update_lease_time, uint32_t update_key_lease_time,
+                                                size_t *NONNULL p_length, dns_wire_t *NULLABLE in_wire,
+                                                uint32_t serial, bool removing);
+int srp_host_key_reset_for_client(client_state_t *NONNULL client);
+
 // Call this before calling anything else.   Context will be passed back whenever the srp code
 // calls any of the host functions.
 int srp_host_init(void *NULLABLE host_context);
diff --git a/ServiceRegistration/srp-client.c b/ServiceRegistration/srp-client.c
index 6b26e10..44c1def 100644
--- a/ServiceRegistration/srp-client.c
+++ b/ServiceRegistration/srp-client.c
@@ -1,6 +1,6 @@
 /* srp-client.c
  *
- * Copyright (c) 2018-2021 Apple Inc. All rights reserved.
+ * Copyright (c) 2018-2023 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -26,13 +26,21 @@
 #include <unistd.h>
 #include <inttypes.h>
 #include <errno.h>
+#include "srp.h"
+#ifdef SRP_TEST_SERVER
+#undef DNSServiceRegister
+#define DNSServiceRegister      srp_client_register
+#undef DNSServiceUpdateRecord
+#define DNSServiceUpdateRecord  srp_client_update_record
+#undef DNSServiceRefDeallocate
+#define DNSServiceRefDeallocate srp_client_ref_deallocate
+#endif
 #ifdef THREAD_DEVKIT_ADK
 #include "../mDNSShared/dns_sd.h"
 #else
 #include <dns_sd.h>
 #include <arpa/inet.h>
 #endif
-#include "srp.h"
 #include "srp-api.h"
 #include "dns-msg.h"
 #include "srp-crypto.h"
@@ -52,7 +60,6 @@
 // this amount of time
 #define INITIAL_NEXT_ATTEMPT_TIME          1000 * 2 * 60
 
-
 typedef struct client_state client_state_t;
 
 typedef struct service_addr service_addr_t;
@@ -140,9 +147,7 @@
 // Forward references
 static int do_srp_update(client_state_t *client, bool definite, bool *did_something);
 static void udp_response(void *v_update_context, void *v_message, size_t message_length);
-static dns_wire_t *NULLABLE generate_srp_update(client_state_t *client, uint32_t update_lease_time,
-                                                uint32_t update_key_lease_time, size_t *NONNULL p_length,
-                                                service_addr_t *NONNULL server, uint32_t serial, bool remove);
+
 static bool srp_is_network_active(void);
 
 #define VALIDATE_IP_ADDR                                         \
@@ -152,6 +157,19 @@
         return kDNSServiceErr_Invalid;                           \
     }
 
+
+client_state_t *
+srp_client_get_current(void)
+{
+    return current_client;
+}
+
+void
+srp_client_set_current(client_state_t *new_client)
+{
+    current_client = new_client;
+}
+
 // Call this before calling anything else.   Context will be passed back whenever the srp code
 // calls any of the host functions.
 int
@@ -175,13 +193,19 @@
 }
 
 int
+srp_host_key_reset_for_client(client_state_t *client)
+{
+    if (client->key != NULL) {
+        srp_keypair_free(client->key);
+        client->key = NULL;
+    }
+    return srp_reset_key("com.apple.srp-client.host-key", client->os_context);
+}
+
+int
 srp_host_key_reset(void)
 {
-    if (current_client->key != NULL) {
-        srp_keypair_free(current_client->key);
-        current_client->key = NULL;
-    }
-    return srp_reset_key("com.apple.srp-client.host-key", current_client->os_context);
+    return srp_host_key_reset_for_client(current_client);
 }
 
 int
@@ -845,7 +869,7 @@
             // In principle if it fails here, it might succeed later, so we just don't send a packet and let
             // the timeout take care of it.
             if (err != kDNSServiceErr_NoError) {
-                ERROR("udp_retransmit: error %d creating udp context.", err);
+                ERROR("udp_retransmit: error %d connecting udp context.", err);
             } else {
                 if (context->server->rr.type == dns_rrtype_a) {
 #ifdef THREAD_DEVKIT_ADK
@@ -880,8 +904,9 @@
         }
 
         if (context->message == NULL) {
-            context->message = generate_srp_update(client, client->lease_time, client->key_lease_time, &context->message_length,
-                                                   context->server, context->serial, context->removing);
+            context->message = srp_client_generate_update(client, client->lease_time, client->key_lease_time,
+                                                          &context->message_length, NULL, context->serial,
+                                                          context->removing);
             if (context->message == NULL) {
                 ERROR("No memory for message.");
                 return;
@@ -1163,9 +1188,9 @@
 }
 
 // Generate a new SRP update message
-static dns_wire_t *
-generate_srp_update(client_state_t *client, uint32_t update_lease_time, uint32_t update_key_lease_time,
-                    size_t *NONNULL p_length, service_addr_t * UNUSED server, uint32_t serial, bool removing)
+dns_wire_t *
+srp_client_generate_update(client_state_t *client, uint32_t update_lease_time, uint32_t update_key_lease_time,
+                           size_t *NONNULL p_length, dns_wire_t *in_wire, uint32_t serial, bool removing)
 {
     dns_wire_t *message;
     const char *zone_name = "default.service.arpa";
@@ -1197,18 +1222,26 @@
 #define CH if (towire.error) { line = __LINE__; goto fail; }
 
     if (client->hostname == NULL) {
-        ERROR("generate_srp_update called with NULL hostname.");
+        ERROR("called with NULL hostname.");
         return NULL;
     }
 
-    // Allocate a message buffer.
-    message = calloc(1, sizeof *message);
-    if (message == NULL) {
-        return NULL;
+    // If we were given a message buffer, use it, otherwise allocate one.
+    if (in_wire != NULL) {
+        message = in_wire;
+        towire.p = &message->data[0];
+        towire.lim = towire.p + *p_length;
+        towire.message = in_wire;
+    } else {
+        // Allocate a message buffer.
+        message = calloc(1, sizeof *message);
+        if (message == NULL) {
+            return NULL;
+        }
+        towire.p = &message->data[0];               // We start storing RR data here.
+        towire.lim = &message->data[DNS_DATA_SIZE]; // This is the limit to how much we can store.
+        towire.message = message;
     }
-    towire.p = &message->data[0];               // We start storing RR data here.
-    towire.lim = &message->data[DNS_DATA_SIZE]; // This is the limit to how much we can store.
-    towire.message = message;
 
     // Generate a random UUID.
     message->id = srp_random16();
@@ -1482,7 +1515,7 @@
         update_finalize(client->active_update);
         client->active_update = NULL;
     }
-    if (message != NULL) {
+    if (in_wire == NULL && message != NULL) {
         free(message);
     }
     return NULL;
@@ -1612,13 +1645,15 @@
     return kDNSServiceErr_NoSuchRecord;
 found:
     rp->removing = true;
-    if (client->active_update->message) {
-        free(client->active_update->message);
-        client->active_update->message = NULL;
+    if (client->active_update != NULL) {
+        if (client->active_update->message) {
+            free(client->active_update->message);
+            client->active_update->message = NULL;
+        }
+        client->active_update->next_retransmission_time = INITIAL_NEXT_RETRANSMISSION_TIME;
+        client->active_update->next_attempt_time = INITIAL_NEXT_ATTEMPT_TIME;
+        udp_retransmit(client->active_update);
     }
-    client->active_update->next_retransmission_time = INITIAL_NEXT_RETRANSMISSION_TIME;
-    client->active_update->next_attempt_time = INITIAL_NEXT_ATTEMPT_TIME;
-    udp_retransmit(client->active_update);
     return kDNSServiceErr_NoError;
 }
 
diff --git a/ServiceRegistration/srp-dnssd.h b/ServiceRegistration/srp-dnssd.h
new file mode 100644
index 0000000..7bf29ec
--- /dev/null
+++ b/ServiceRegistration/srp-dnssd.h
@@ -0,0 +1,95 @@
+/* srp-dnssd.h
+ *
+ * Copyright (c) 2023 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * DNSSD intercept API for testing srp-mdns-proxy
+ */
+
+#ifdef SRP_TEST_SERVER
+typedef struct srp_server_state srp_server_t;
+DNSServiceErrorType dns_service_register(srp_server_t *NULLABLE srp_server, DNSServiceRef NONNULL *NULLABLE sdRef,
+                                         DNSServiceFlags flags, uint32_t interfaceIndex, const char *NULLABLE name,
+                                         const char *NONNULL regtype, const char *NULLABLE domain,
+                                         const char *NULLABLE host, uint16_t port, uint16_t txtLen,
+                                         const void *NULLABLE txtRecord, DNSServiceRegisterReply NULLABLE callBack,
+                                         void *NULLABLE context);
+DNSServiceErrorType dns_service_register_wa(srp_server_t *NULLABLE srp_server, DNSServiceRef NONNULL *NULLABLE sdRef,
+                                            DNSServiceFlags flags, uint32_t interfaceIndex, const char *NULLABLE name,
+                                            const char *NONNULL regtype, const char *NULLABLE domain,
+                                            const char *NULLABLE host, uint16_t port, uint16_t txtLen,
+                                            const void *NULLABLE txtRecord, DNSServiceAttributeRef NULLABLE attr,
+                                            DNSServiceRegisterReply NULLABLE callBack, void *NULLABLE context);
+DNSServiceErrorType dns_service_register_record(srp_server_t *NULLABLE srp_server, DNSServiceRef NONNULL sdRef,
+                                                DNSRecordRef NONNULL *NULLABLE RecordRef, DNSServiceFlags flags,
+                                                uint32_t interfaceIndex, const char *NONNULL fullname, uint16_t rrtype,
+                                                uint16_t rrclass, uint16_t rdlen, const void *NONNULL rdata,
+                                                uint32_t ttl, DNSServiceRegisterRecordReply NULLABLE callBack,
+                                                void *NULLABLE context);
+DNSServiceErrorType dns_service_register_record_wa(srp_server_t *NULLABLE srp_server, DNSServiceRef NONNULL sdRef,
+                                                   DNSRecordRef NONNULL *NULLABLE RecordRef, DNSServiceFlags flags,
+                                                   uint32_t interfaceIndex, const char *NONNULL fullname,
+                                                   uint16_t rrtype, uint16_t rrclass, uint16_t rdlen,
+                                                   const void *NONNULL rdata, uint32_t ttl,
+                                                   DNSServiceAttributeRef NULLABLE attr,
+                                                   DNSServiceRegisterRecordReply NULLABLE callBack,
+                                                   void *NULLABLE context);
+DNSServiceErrorType dns_service_remove_record(srp_server_t *NULLABLE srp_server, DNSServiceRef NONNULL sdRef,
+                                              DNSRecordRef NONNULL RecordRef, DNSServiceFlags flags);
+DNSServiceErrorType dns_service_update_record(srp_server_t *NULLABLE srp_server, DNSServiceRef NONNULL sdRef,
+                                              DNSRecordRef NULLABLE recordRef, DNSServiceFlags flags, uint16_t rdlen,
+                                              const void *NONNULL rdata, uint32_t ttl);
+DNSServiceErrorType dns_service_update_record_wa(srp_server_t *NULLABLE srp_server, DNSServiceRef NONNULL sdRef,
+                                                 DNSRecordRef NULLABLE recordRef, DNSServiceFlags flags, uint16_t rdlen,
+                                                 const void *NULLABLE rdata, uint32_t ttl,
+                                                 DNSServiceAttributeRef NULLABLE attr);
+void dns_service_ref_deallocate(srp_server_t *NULLABLE srp_server, DNSServiceRef NONNULL sdRef);
+void ioloop_dnssd_txn_cancel_srp(void *NULLABLE srp_server, dnssd_txn_t *NONNULL txn);
+DNSServiceErrorType
+dns_service_query_record(srp_server_t *NULLABLE srp_server, DNSServiceRef NONNULL *NULLABLE sdRef,
+                         DNSServiceFlags flags, uint32_t interfaceIndex, const char *NONNULL fullname,
+                         uint16_t rrtype, uint16_t rrclass, DNSServiceQueryRecordReply NONNULL callBack,
+                         void *NULLABLE context);
+DNSServiceErrorType
+dns_service_query_record_wa(srp_server_t *NULLABLE srp_server, DNSServiceRef NONNULL *NULLABLE sdRef,
+                            DNSServiceFlags flags, uint32_t interfaceIndex, const char *NONNULL fullname,
+                            uint16_t rrtype, uint16_t rrclass, DNSServiceAttribute const *NULLABLE attr,
+                            DNSServiceQueryRecordReply NONNULL callBack, void *NULLABLE context);
+dnssd_txn_t *NULLABLE
+dns_service_ioloop_txn_add(srp_server_t *NULLABLE srp_server, DNSServiceRef NONNULL sdref, void *NULLABLE context,
+                           dnssd_txn_finalize_callback_t NULLABLE finalize_callback,
+                           dnssd_txn_failure_callback_t NULLABLE failure_callback);
+#else
+#define dns_service_ref_deallocate(srp_server, ...)     DNSServiceRefDeallocate(__VA_ARGS__)
+#define dns_service_register_record(srp_server, ...)    DNSServiceRegisterRecord(__VA_ARGS__)
+#define dns_service_register_record_wa(srp_server, ...) DNSServiceRegisterRecordWithAttribute(__VA_ARGS__)
+#define dns_service_register(srp_server, ...)           DNSServiceRegister(__VA_ARGS__)
+#define dns_service_register_wa(srp_server, ...)        DNSServiceRegisterWithAttribute(__VA_ARGS__)
+#define dns_service_remove_record(srp_server, ...)      DNSServiceRemoveRecord(__VA_ARGS__)
+#define dns_service_update_record(srp_server, ...)      DNSServiceUpdateRecord(__VA_ARGS__)
+#define dns_service_update_record_wa(srp_server, ...)   DNSServiceUpdateRecordWithAttribute(__VA_ARGS__)
+#define ioloop_dnssd_txn_cancel_srp(srp_server, ...)    ioloop_dnssd_txn_cancel(__VA_ARGS__)
+#define dns_service_query_record(srp_server, ...)       DNSServiceQueryRecord(__VA_ARGS__)
+#define dns_service_query_record_wa(srp_server, ...)    DNSServiceQueryRecordWithAttribute(__VA_ARGS__)
+#define dns_service_ioloop_txn_add(srp_server, ...)     ioloop_dnssd_txn_add(__VA_ARGS__)
+#endif
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/srp-features.h b/ServiceRegistration/srp-features.h
index 465eda0..b1559a2 100644
--- a/ServiceRegistration/srp-features.h
+++ b/ServiceRegistration/srp-features.h
@@ -1,6 +1,6 @@
 /* srp-features.h
  *
- * Copyright (c) 2020-2021 Apple Inc. All rights reserved.
+ * Copyright (c) 2020-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -23,6 +23,14 @@
 
 // SRP_FEATURE_COMBINED_SRP_DNSSD_PROXY: controls whether to initialize dnssd-proxy in srp-mdns-proxy.
 #if defined(BUILD_SRP_MDNS_PROXY) && (BUILD_SRP_MDNS_PROXY == 1)
+
+// SRP_TEST_SERVER always builds a full (simulated) Thread border router
+#  if defined(SRP_TEST_SERVER)
+#    define SRP_TEST_SERVER_OVERRIDE 1
+#  else
+#    define SRP_TEST_SERVER_OVERRIDE 0
+#  endif
+
 // We can only have combined srp-dnssd-proxy if we are building srp-mdns-proxy
 #  define SRP_FEATURE_PUBLISH_SPECIFIC_ROUTES 0
 #  define SRP_FEATURE_DNSSD_PROXY_SHARED_CONNECTION 0
@@ -57,6 +65,7 @@
 
     #define SRP_ANALYTICS 0
 
+
 // At present we never want this, but we're keeping the code around.
 #define SRP_ALLOWS_MDNS_CONFLICTS 0
 
diff --git a/ServiceRegistration/srp-gw.h b/ServiceRegistration/srp-gw.h
index e9e86f3..b779246 100644
--- a/ServiceRegistration/srp-gw.h
+++ b/ServiceRegistration/srp-gw.h
@@ -74,6 +74,7 @@
     service_t *NONNULL service;
     int num_instances;
     dns_rr_t *NULLABLE srv, *NULLABLE txt;
+    bool skip_update;
 };
 
 // The update_t structure is used to maintain the ongoing state of a particular DNS Update.
diff --git a/ServiceRegistration/srp-ioloop.c b/ServiceRegistration/srp-ioloop.c
index 4c7e76b..df63bea 100644
--- a/ServiceRegistration/srp-ioloop.c
+++ b/ServiceRegistration/srp-ioloop.c
@@ -1,6 +1,6 @@
 /* srp-ioloop.c
  *
- * Copyright (c) 2019-2022 Apple Inc. All rights reserved.
+ * Copyright (c) 2019-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -50,8 +50,12 @@
 static int num_clients = 1;
 static int bogusify_signatures = false;
 static int bogus_remove = false;
+static int push_hardwired = false;
 static int push_query = false;
 static int push_unsubscribe = false;
+static bool push_subscribe_sent = false;
+static uint16_t dns_push_subscribe_xid;
+static int push_send_bogus_keepalive = false;
 static int push_exhaust = false;
 static bool test_subtypes = false;
 static bool test_renew_subtypes = false;
@@ -62,6 +66,7 @@
 extern bool zero_addresses;
 static bool expire_instance = false;
 static bool test_bad_sig_time = false;
+static bool do_srp = true;
 
 const uint64_t thread_enterprise_number = 52627;
 
@@ -70,6 +75,7 @@
 static const char *interface_name = NULL;
 static wakeup_t *wait_for_remote_disconnect = NULL;
 static dso_state_t *disconnect_expected = NULL;
+os_log_t global_os_log;
 
 #define SRP_IO_CONTEXT_MAGIC 0xFEEDFACEFADEBEEFULL  // BEES!   Everybody gets BEES!
 typedef struct io_context {
@@ -93,6 +99,8 @@
 } srp_client_t;
 
 static void start_push_query(void);
+static void send_push_unsubscribe(void);
+static void send_push_subscribe(void);
 
 static int
 validate_io_context(io_context_t **dest, void *src)
@@ -325,7 +333,7 @@
 }
 
 static void
-interface_callback(void *context, const char *NONNULL name,
+interface_callback(srp_server_t *UNUSED NULLABLE server_state, void *context, const char *NONNULL name,
                    const addr_t *NONNULL address, const addr_t *NONNULL netmask,
                    uint32_t flags, enum interface_address_change event_type)
 {
@@ -572,23 +580,24 @@
     uint8_t *buffer = (uint8_t *)&dns_message;
     dns_towire_state_t towire;
     dso_message_t message;
-    dso_make_message(&message, buffer, sizeof(dns_message), dso_connection->dso, true, false, 0, 0, NULL);
-    memset(&towire, 0, sizeof(towire));
-    towire.p = &buffer[DNS_HEADER_SIZE];
-    towire.lim = towire.p + (sizeof(dns_message) - DNS_HEADER_SIZE);
-    towire.message = &dns_message;
-    dns_u16_to_wire(&towire, kDSOType_DNSPushUnsubscribe);
-    dns_rdlength_begin(&towire);
-    dns_full_name_to_wire(NULL, &towire, "_hap._udp.openthread.thread.home.arpa");
-    dns_u16_to_wire(&towire, dns_rrtype_ptr);
-    dns_u16_to_wire(&towire, dns_qclass_in);
-    dns_rdlength_end(&towire);
+    if (!push_send_bogus_keepalive) {
+        INFO("unsubscribe");
+        dso_make_message(&message, buffer, sizeof(dns_message), dso_connection->dso, true, false, 0, 0, NULL);
+        memset(&towire, 0, sizeof(towire));
+        towire.p = &buffer[DNS_HEADER_SIZE];
+        towire.lim = towire.p + (sizeof(dns_message) - DNS_HEADER_SIZE);
+        towire.message = &dns_message;
+        dns_u16_to_wire(&towire, kDSOType_DNSPushUnsubscribe);
+        dns_rdlength_begin(&towire);
+        dns_u16_to_wire(&towire, dns_push_subscribe_xid);
+        dns_rdlength_end(&towire);
 
-    memset(&iov, 0, sizeof(iov));
-    iov.iov_len = towire.p - buffer;
-    iov.iov_base = buffer;
-    ioloop_send_message(dso_connection, NULL, &iov, 1);
-    subscribe_xid = dns_message.id; // We need this to identify the response.
+        memset(&iov, 0, sizeof(iov));
+        iov.iov_len = towire.p - buffer;
+        iov.iov_base = buffer;
+        ioloop_send_message(dso_connection, NULL, &iov, 1);
+        subscribe_xid = dns_message.id; // We need this to identify the response.
+    }
 
     // Send a keepalive message so that we can get the response, since the unsubscribe is not a response-requiring request.
     dso_make_message(&message, buffer, sizeof(dns_message), dso_connection->dso, false, false, 0, 0, NULL);
@@ -601,6 +610,11 @@
     dns_u32_to_wire(&towire, 600);
     dns_u32_to_wire(&towire, 600);
     dns_rdlength_end(&towire);
+    if (push_send_bogus_keepalive) {
+        INFO("sending bogus keepalive");
+        // Send a badly formatted message.
+        dns_u32_to_wire(&towire, 0x12345678);
+    }
     keepalive_xid = dns_message.id;
     memset(&iov, 0, sizeof(iov));
     iov.iov_len = towire.p - buffer;
@@ -656,6 +670,10 @@
         } else {
             INFO("Keepalive from server");
         }
+        if (!push_subscribe_sent) {
+            send_push_subscribe();
+            push_subscribe_sent = true;
+        }
         break;
 
     case kDSOType_DNSPushSubscribe:
@@ -793,6 +811,10 @@
         INFO("should send a keepalive now.");
         break;
     case kDSOEventType_KeepaliveRcvd:
+        if (!push_subscribe_sent) {
+            send_push_subscribe();
+            push_subscribe_sent = true;
+        }
         INFO("keepalive received.");
         break;
     case kDSOEventType_RetryDelay:
@@ -804,6 +826,40 @@
 }
 
 static void
+send_push_subscribe(void)
+{
+    struct iovec iov;
+    INFO("have session");
+    dns_wire_t dns_message;
+    uint8_t *buffer = (uint8_t *)&dns_message;
+    dns_towire_state_t towire;
+    dso_message_t message;
+    dso_make_message(&message, buffer, sizeof(dns_message), dso_connection->dso, false, false, 0, 0, NULL);
+    dns_push_subscribe_xid = ntohs(dns_message.id);
+    memset(&towire, 0, sizeof(towire));
+    towire.p = &buffer[DNS_HEADER_SIZE];
+    towire.lim = towire.p + (sizeof(dns_message) - DNS_HEADER_SIZE);
+    towire.message = &dns_message;
+    dns_u16_to_wire(&towire, kDSOType_DNSPushSubscribe);
+    dns_rdlength_begin(&towire);
+    if (push_hardwired) {
+        dns_full_name_to_wire(NULL, &towire, "default.service.arpa");
+        dns_u16_to_wire(&towire, dns_rrtype_soa);
+    } else {
+        dns_full_name_to_wire(NULL, &towire, "_airplay._tcp.default.service.arpa");
+        dns_u16_to_wire(&towire, dns_rrtype_ptr);
+    }
+    dns_u16_to_wire(&towire, dns_qclass_in);
+    dns_rdlength_end(&towire);
+
+    memset(&iov, 0, sizeof(iov));
+    iov.iov_len = towire.p - buffer;
+    iov.iov_base = buffer;
+    ioloop_send_message(dso_connection, NULL, &iov, 1);
+    subscribe_xid = dns_message.id; // We need this to identify the response.
+}
+
+static void
 dso_connected(comm_t *connection, void *UNUSED context)
 {
     struct iovec iov;
@@ -823,18 +879,16 @@
     towire.p = &buffer[DNS_HEADER_SIZE];
     towire.lim = towire.p + (sizeof(dns_message) - DNS_HEADER_SIZE);
     towire.message = &dns_message;
-    dns_u16_to_wire(&towire, kDSOType_DNSPushSubscribe);
+    dns_u16_to_wire(&towire, kDSOType_Keepalive);
     dns_rdlength_begin(&towire);
-    dns_full_name_to_wire(NULL, &towire, "_hap._udp.openthread.thread.home.arpa");
-    dns_u16_to_wire(&towire, dns_rrtype_ptr);
-    dns_u16_to_wire(&towire, dns_qclass_in);
+    dns_u32_to_wire(&towire, 100); // Inactivity timeout
+    dns_u32_to_wire(&towire, 100); // Keepalive interval
     dns_rdlength_end(&towire);
 
     memset(&iov, 0, sizeof(iov));
     iov.iov_len = towire.p - buffer;
     iov.iov_base = buffer;
     ioloop_send_message(dso_connection, NULL, &iov, 1);
-    subscribe_xid = dns_message.id; // We need this to identify the response.
 }
 
 static void
@@ -918,7 +972,7 @@
 
     if (!new_ip_dup && !zero_addresses) {
         srp_start_address_refresh();
-        ioloop_map_interface_addresses(interface_name, services, interface_callback);
+        ioloop_map_interface_addresses(NULL, interface_name, services, interface_callback);
     }
     for (i = 0; i < services->num; i++) {
         cti_service_t *cti_service = services->services[i];
@@ -1041,10 +1095,24 @@
             bogus_remove = true;
         } else if (!strcmp(argv[i], "--push-query")) {
             push_query = true;
-        } else if (!strcmp(argv[i], "--push-unsubscribe")) {
+            do_srp = false;
+        } else if (!strcmp(argv[i], "--push-hardwired")) {
+            push_query = true;
+            push_hardwired = true;
             push_unsubscribe = true;
+            do_srp = false;
+        } else if (!strcmp(argv[i], "--push-unsubscribe")) {
+            push_query = true;
+            push_unsubscribe = true;
+            do_srp = false;
+        } else if (!strcmp(argv[i], "--push-send-bogus-keepalive")) {
+            push_query = true;
+            push_unsubscribe = true;
+            push_send_bogus_keepalive = true;
+            do_srp = false;
         } else if (!strcmp(argv[i], "--push-exhaust")) {
             push_exhaust = true;
+            do_srp = false;
         } else if (!strcmp(argv[i], "--test-subtypes")) {
             test_subtypes = true;
         } else if (!strcmp(argv[i], "--test-diff-subtypes")) {
@@ -1096,7 +1164,7 @@
     }
 
     if (!use_thread_services && !new_ip_dup && !zero_addresses) {
-        ioloop_map_interface_addresses(interface_name, NULL, interface_callback);
+        ioloop_map_interface_addresses(NULL, interface_name, NULL, interface_callback);
     }
 
     if (!have_server_address && !use_thread_services) {
@@ -1231,10 +1299,12 @@
         }
     }
 
-    if (use_thread_services) {
-        cti_get_service_list(NULL, &thread_service_context, NULL, cti_service_list_callback, NULL);
-    } else {
-        srp_network_state_stable(NULL);
+    if (do_srp) {
+        if (use_thread_services) {
+            cti_get_service_list(NULL, &thread_service_context, NULL, cti_service_list_callback, NULL);
+        } else {
+            srp_network_state_stable(NULL);
+        }
     }
     ioloop();
 }
diff --git a/ServiceRegistration/srp-log.c b/ServiceRegistration/srp-log.c
index a88639a..52fd315 100644
--- a/ServiceRegistration/srp-log.c
+++ b/ServiceRegistration/srp-log.c
@@ -147,6 +147,35 @@
     return "<INVALID dns_rrtype>";
 }
 
+#ifdef LOG_FPRINTF_STDERR
+bool srp_log_timestamp_relative = false;
+
+void
+srp_log_timestamp(char *buf, size_t bufsize)
+{
+    if (srp_log_timestamp_relative) {
+        double srp_fractional_time(void);
+        static bool have_initial_timestamp = false;
+        static double initial_timestamp = 0.0;
+        if (!have_initial_timestamp) {
+            have_initial_timestamp = true;
+            initial_timestamp = srp_fractional_time();
+        }
+        snprintf(buf, bufsize, "%6.6lf", srp_fractional_time() - initial_timestamp);
+    } else {
+        char timebuf[20]; // YYYY-MM-DD HH:MM:SS
+        char zonebuf[6]; // [-+]HMM
+        struct timeval tv;
+        struct tm tm;
+        gettimeofday(&tv, NULL);
+        localtime_r(&tv.tv_sec, &tm);
+        strftime(timebuf, sizeof(timebuf), "%F %T", &tm);
+        strftime(zonebuf, sizeof(zonebuf), "%z", &tm);
+        snprintf(buf, bufsize, "%s.%06d%s", timebuf, tv.tv_usec, zonebuf);
+    }
+}
+#endif
+
 // Local Variables:
 // mode: C
 // tab-width: 4
diff --git a/ServiceRegistration/srp-log.h b/ServiceRegistration/srp-log.h
index 30b6498..dacb1eb 100644
--- a/ServiceRegistration/srp-log.h
+++ b/ServiceRegistration/srp-log.h
@@ -36,7 +36,7 @@
 #endif // #ifdef __APPLE__
 
 #ifdef DEBUG
-    #undef DEBUG
+#    undef DEBUG
     // #define DEBUG_VERBOSE
 #endif
 
@@ -47,111 +47,115 @@
 // MARK: - Log Macros
 
 #ifdef FUZZING
-    #define OPENLOG(progname, consolep)
-    #define ERROR(fmt, ...)
-    #define INFO(fmt, ...)
-    #define DEBUG(fmt, ...)
-    #define FAULT(fmt, ...)
+#    define OPENLOG(progname, consolep)
+#    define ERROR(fmt, ...)
+#    define INFO(fmt, ...)
+#    define DEBUG(fmt, ...)
+#    define FAULT(fmt, ...)
 #elif defined(THREAD_DEVKIT_ADK)
 
-    #include "srp-platform.h"
+#    include "srp-platform.h"
 
-    #define OPENLOG(progname, consolep) srp_openlog(option)
-    #define ERROR(fmt, ...)   srp_log_error(fmt, ##__VA_ARGS__)
-    #define INFO(fmt, ...)    srp_log_info(fmt, ##__VA_ARGS__)
-    #ifdef DEBUG_VERBOSE
-        #define DEBUG(fmt, ...) srp_log_debug(fmt, ##__VA_ARGS__)
-    #else
-        #define DEBUG(fmt, ...)
-    #endif // DEBUG VERBOSE
-    #define FAULT(fmt, ...) srp_log_error(fmt, ##__VA_ARGS__)
-    #define NO_CLOCK
+#    define OPENLOG(progname, consolep) srp_openlog(option)
+#    define ERROR(fmt, ...)   srp_log_error(fmt, ##__VA_ARGS__)
+#    define INFO(fmt, ...)    srp_log_info(fmt, ##__VA_ARGS__)
+#    ifdef DEBUG_VERBOSE
+#        define DEBUG(fmt, ...) srp_log_debug(fmt, ##__VA_ARGS__)
+#    else
+#        define DEBUG(fmt, ...)
+#    endif // DEBUG VERBOSE
+#    define FAULT(fmt, ...) srp_log_error(fmt, ##__VA_ARGS__)
+#    define NO_CLOCK
 #else // ifdef THREAD_DEVKIT_ADK
 
-    #ifdef LOG_FPRINTF_STDERR
-        #define OPENLOG(progname, consolep) do { (void)(consolep); (void)progname; } while (0)
-        #define ERROR(fmt, ...) fprintf(stderr, "%s: " fmt "\n", __FUNCTION__, ##__VA_ARGS__)
-        #define INFO(fmt, ...)  fprintf(stderr, "%s: " fmt "\n", __FUNCTION__, ##__VA_ARGS__)
-        #define FAULT(fmt, ...) fprintf(stderr, "%s: " fmt "\n", __FUNCTION__, ##__VA_ARGS__)
-        #ifdef DEBUG_VERBOSE
-            #ifdef IOLOOP_MACOS
+#    ifdef LOG_FPRINTF_STDERR
+void srp_log_timestamp(char *buf, size_t bufsize);
+extern bool srp_log_timestamp_relative;
+#        define OPENLOG(progname, consolep) do { (void)(consolep); (void)progname; } while (0)
+#        define SRP_LOG_TIME char srp_log_time_buf[32]; srp_log_timestamp(srp_log_time_buf, sizeof(srp_log_time_buf))
+#        define ERROR(fmt, ...) do { SRP_LOG_TIME; fprintf(stderr, "%s %s: " fmt "\n", srp_log_time_buf, __FUNCTION__, ##__VA_ARGS__); } while (0)
+#        define INFO(fmt, ...)  do { SRP_LOG_TIME; fprintf(stderr, "%s %s: " fmt "\n", srp_log_time_buf, __FUNCTION__, ##__VA_ARGS__); } while (0)
+#        ifdef DEBUG_VERBOSE
+#            ifdef IOLOOP_MACOS
                 int get_num_fds(void);
-            #endif // ifdef IOLOOP_MACOS
-            #define DEBUG(fmt, ...) fprintf(stderr, "%s: " fmt "\n", __FUNCTION__, ##__VA_ARGS__)
-        #else // ifdef DEBUG_VERBOSE
-            #define DEBUG(fmt, ...)
-        #endif
-        #define FAULT(fmt, ...) fprintf(stderr, "%s: " fmt "\n", __FUNCTION__, ##__VA_ARGS__)
-    #else // ifdef LOG_FPRINTF_STDERR
-        #include <syslog.h>
+#            endif // ifdef IOLOOP_MACOS
+#            define DEBUG(fmt, ...)  do { SRP_LOG_TIME; fprintf(stderr, "%s %s: " fmt "\n", srp_log_time_buf, __FUNCTION__, ##__VA_ARGS__); } while (0)
+#        else // ifdef DEBUG_VERBOSE
+#            define DEBUG(fmt, ...)
+#        endif
+#        define FAULT(fmt, ...) do { SRP_LOG_TIME; fprintf(stderr, "%s %s: " fmt "\n", srp_log_time_buf, __FUNCTION__, ##__VA_ARGS__); } while (0)
+#    else // ifdef LOG_FPRINTF_STDERR
+#        include <syslog.h>
 
         // Apple device always has OS_LOG support.
-        #ifdef __APPLE__
-            #define OS_LOG_ENABLED 1
-            #include <os/log.h>
+#        ifdef __APPLE__
+#            define OS_LOG_ENABLED 1
+#            include <os/log.h>
+extern os_log_t global_os_log;
+
 
             // Define log level
-            #define LOG_TYPE_FAULT      OS_LOG_TYPE_FAULT
-            #define LOG_TYPE_ERROR      OS_LOG_TYPE_ERROR
-            #define LOG_TYPE_DEFAULT    OS_LOG_TYPE_DEFAULT
-            #define LOG_TYPE_DEBUG      OS_LOG_TYPE_DEBUG
+#            define LOG_TYPE_FAULT      OS_LOG_TYPE_FAULT
+#            define LOG_TYPE_ERROR      OS_LOG_TYPE_ERROR
+#            define LOG_TYPE_INFO       OS_LOG_TYPE_DEFAULT
+#            define LOG_TYPE_DEBUG      OS_LOG_TYPE_DEBUG
             // Define log macro
-            #define log_with_component_and_type(CATEGORY, LEVEL, FORMAT, ...) \
-                os_log_with_type((CATEGORY), (LEVEL), ("%{public}s: " FORMAT), __FUNCTION__, ##__VA_ARGS__)
+#            define SRP_OS_LOG(component, type, format, ...) \
+                os_log_with_type((component), (type), ("%{public}s: " format), __FUNCTION__, ##__VA_ARGS__)
 
-            #define OPENLOG(progname, consolep) \
-                do { if (consolep) { putenv("ACTIVITY_LOG_STDERR=1"); } (void)progname; } while (0)
-            #define FAULT(FORMAT, ...)  log_with_component_and_type(OS_LOG_DEFAULT, LOG_TYPE_FAULT, \
-                                            FORMAT, ##__VA_ARGS__)
-            #define ERROR(FORMAT, ...)  log_with_component_and_type(OS_LOG_DEFAULT, LOG_TYPE_ERROR, \
-                                            FORMAT, ##__VA_ARGS__)
+#            define OPENLOG(progname, consolep) \
+                do { \
+                    if (consolep) {                                         \
+                        putenv("ACTIVITY_LOG_STDERR=1"); \
+                    } \
+                    (void)progname; \
+                    global_os_log = os_log_create("com.apple.srp-mdns-proxy", "0"); \
+                } while (0)
+#            define FAULT(format, ...)  SRP_OS_LOG(global_os_log, LOG_TYPE_FAULT, format, ##__VA_ARGS__)
+#            define ERROR(format, ...)  SRP_OS_LOG(global_os_log, LOG_TYPE_ERROR, format, ##__VA_ARGS__)
 
-            #ifdef DEBUG_VERBOSE
-                #ifdef DEBUG_FD_LEAKS
+#            ifdef DEBUG_VERBOSE
+#                ifdef DEBUG_FD_LEAKS
                     int get_num_fds(void);
-                    #define INFO(FORMAT, ...) \
+#                    define INFO(format, ...) \
                         do { \
                             int foo = get_num_fds(); \
-                            log_with_component_and_type(OS_LOG_DEFAULT, LOG_TYPE_DEFAULT, "%d " FORMAT, foo, \
-                                ##__VA_ARGS__); \
+                            SRP_OS_LOG(global_os_log, LOG_TYPE_INFO, "%d " format, foo, ##__VA_ARGS__); \
                         } while(0)
-                #else // ifdef IOLOOP_MACOS
-                    #define INFO(FORMAT, ...)   log_with_component_and_type(OS_LOG_DEFAULT, \
-                                                    LOG_TYPE_DEFAULT, FORMAT, ##__VA_ARGS__)
-                #endif // ifdef IOLOOP_MACOS
+#                else // ifdef IOLOOP_MACOS
+#                    define INFO(format, ...) SRP_OS_LOG(global_os_log, LOG_TYPE_INFO, format, ##__VA_ARGS__)
+#                endif // ifdef IOLOOP_MACOS
 
-                #define DEBUG(FORMAT, ...)  log_with_component_and_type(OS_LOG_DEFAULT, \
-                                                LOG_TYPE_DEBUG, FORMAT, ##__VA_ARGS__)
-            #else // ifdef DEBUG_VERBOSE
-                #define INFO(FORMAT, ...)   log_with_component_and_type(OS_LOG_DEFAULT, \
-                                                LOG_TYPE_DEFAULT, FORMAT, ##__VA_ARGS__)
-                #define DEBUG(FORMAT, ...)  do {} while(0)
-            #endif // ifdef DEBUG_VERBOSE
-        #else // ifdef __APPLE__
-            #define OS_LOG_ENABLED 0
+#                define DEBUG(format, ...) SRP_OS_LOG(global_os_log, LOG_TYPE_DEBUG, format, ##__VA_ARGS__)
+#            else // ifdef DEBUG_VERBOSE
+#                define INFO(format, ...) SRP_OS_LOG(global_os_log, LOG_TYPE_INFO, format, ##__VA_ARGS__)
+#                define DEBUG(format, ...)  do {} while(0)
+#            endif // ifdef DEBUG_VERBOSE
+#        else // ifdef __APPLE__
+#            define OS_LOG_ENABLED 0
 
-            #define OPENLOG(progname, consolep) openlog(progname, (consolep ? LOG_PERROR : 0) | LOG_PID, LOG_DAEMON)
-            #define FAULT(fmt, ...) syslog(LOG_CRIT, "%s: " fmt, __FUNCTION__, ##__VA_ARGS__)
-            #define ERROR(fmt, ...) syslog(LOG_ERR, "%s: " fmt, __FUNCTION__, ##__VA_ARGS__)
+#            define OPENLOG(progname, consolep) openlog(progname, (consolep ? LOG_PERROR : 0) | LOG_PID, LOG_DAEMON)
+#            define FAULT(fmt, ...) syslog(LOG_CRIT, "%s: " fmt, __FUNCTION__, ##__VA_ARGS__)
+#            define ERROR(fmt, ...) syslog(LOG_ERR, "%s: " fmt, __FUNCTION__, ##__VA_ARGS__)
 
-            #ifdef DEBUG_VERBOSE
-                #ifdef DEBUG_FD_LEAKS
+#            ifdef DEBUG_VERBOSE
+#                ifdef DEBUG_FD_LEAKS
                     int get_num_fds(void);
-                    #define INFO(fmt, ...) \
+#                    define INFO(fmt, ...) \
                         do { \
                             int foo = get_num_fds(); \
                             syslog(LOG_INFO, "%s: %d " fmt, __FUNCTION__, foo, ##__VA_ARGS__); \
                         } while (0)
-                #else // ifdef IOLOOP_MACOS
-                    #define INFO(fmt, ...)  syslog(LOG_INFO, "%s: " fmt, __FUNCTION__, ##__VA_ARGS__)
-                #endif // ifdef IOLOOP_MACOS
-                #define DEBUG(fmt, ...) syslog(LOG_DEBUG, "%s: " fmt, __FUNCTION__, ##__VA_ARGS__)
-            #else // ifdef DEBUG_VERBOSE
-                #define INFO(fmt, ...)  syslog(LOG_INFO, "%s: " fmt, __FUNCTION__, ##__VA_ARGS__)
-                #define DEBUG(fmt, ...) do {} while(0)
-            #endif // ifdef DEBUG_VERBOSE
-        #endif // ifdef __APPLE__
-    #endif // ifdef LOG_FPRINTF_STDERR
+#                else // ifdef IOLOOP_MACOS
+#                    define INFO(fmt, ...)  syslog(LOG_INFO, "%s: " fmt, __FUNCTION__, ##__VA_ARGS__)
+#                endif // ifdef IOLOOP_MACOS
+#                define DEBUG(fmt, ...) syslog(LOG_DEBUG, "%s: " fmt, __FUNCTION__, ##__VA_ARGS__)
+#            else // ifdef DEBUG_VERBOSE
+#                define INFO(fmt, ...)  syslog(LOG_INFO, "%s: " fmt, __FUNCTION__, ##__VA_ARGS__)
+#                define DEBUG(fmt, ...) do {} while(0)
+#            endif // ifdef DEBUG_VERBOSE
+#        endif // ifdef __APPLE__
+#    endif // ifdef LOG_FPRINTF_STDERR
 #endif // ifdef THREAD_DEVKIT_ADK
 
 //======================================================================================================================
@@ -162,13 +166,13 @@
  * would be shown as a hashing value, which could be used as a way to associate other SRP logs even if it's redacted.
  *
  * On Apple platforms, the current existing log routines will be defined as:
- * #define log_with_component_and_type(CATEGORY, LEVEL, FORMAT, ...) os_log_with_type((CATEGORY), (LEVEL), (FORMAT), \
+ * #define log_with_component_and_type(CATEGORY, LEVEL, format, ...) os_log_with_type((CATEGORY), (LEVEL), (format), \
  *                                                                      ##__VA_ARGS__)
- * #define ERROR(FORMAT, ...)  log_with_component_and_type(OS_LOG_DEFAULT, LOG_TYPE_ERROR, FORMAT, ##__VA_ARGS__)
- * #define INFO(FORMAT, ...)   log_with_component_and_type(OS_LOG_DEFAULT, LOG_TYPE_DEFAULT, FORMAT, ##__VA_ARGS__)
- * #define DEBUG(FORMAT, ...)  log_with_component_and_type(OS_LOG_DEFAULT, LOG_TYPE_DEBUG, FORMAT, ##__VA_ARGS__)
+ * #define ERROR(format, ...)  log_with_component_and_type(OS_LOG_DEFAULT, LOG_TYPE_ERROR, format, ##__VA_ARGS__)
+ * #define INFO(format, ...)   log_with_component_and_type(OS_LOG_DEFAULT, LOG_TYPE_DEFAULT, format, ##__VA_ARGS__)
+ * #define DEBUG(format, ...)  log_with_component_and_type(OS_LOG_DEFAULT, LOG_TYPE_DEBUG, format, ##__VA_ARGS__)
  * And to follow the same log level with os_log, FAULT() is defined.
- * #define FAULT(FORMAT, ...)  log_with_component_and_type(OS_LOG_DEFAULT, LOG_TYPE_FAULT, FORMAT, ##__VA_ARGS__)
+ * #define FAULT(format, ...)  log_with_component_and_type(OS_LOG_DEFAULT, LOG_TYPE_FAULT, format, ##__VA_ARGS__)
  * Therefore, all the previous logs would be put under OS_LOG_DEFAULT category.
  * FAULT level lof will be mapped to  LOG_TYPE_FAULT in os_log.
  * ERROR level log will be mapped to LOG_TYPE_ERROR in os_log.
@@ -284,64 +288,64 @@
 #if OS_LOG_ENABLED
     // Define log specifier
     // String
-    #define PUB_S_SRP "%{public}s"
-    #define PRI_S_SRP "%{private, mask.hash}s"
+#    define PUB_S_SRP "%{public}s"
+#    define PRI_S_SRP "%{private, mask.hash}s"
     // DNS name, when the pointer to DNS name is NULL, <NULL> will be logged.
-    #define DNS_NAME_GEN_SRP(NAME, BUF_NAME) \
+#    define DNS_NAME_GEN_SRP(NAME, BUF_NAME) \
         char BUF_NAME[DNS_MAX_NAME_SIZE_ESCAPED + 1]; \
         if (NAME != NULL) { \
             dns_name_print(NAME, BUF_NAME, sizeof(BUF_NAME)); \
         } else { \
             snprintf(BUF_NAME, sizeof(BUF_NAME), "<null>"); \
         }
-    #define PUB_DNS_NAME_SRP PUB_S_SRP
-    #define PRI_DNS_NAME_SRP PRI_S_SRP
-    #define DNS_NAME_PARAM_SRP(NAME, BUF) (BUF)
+#    define PUB_DNS_NAME_SRP PUB_S_SRP
+#    define PRI_DNS_NAME_SRP PRI_S_SRP
+#    define DNS_NAME_PARAM_SRP(NAME, BUF) (BUF)
     // IP address
     // IPv4
-    #define IPv4_ADDR_GEN_SRP(ADDR, BUF_NAME) do {} while(0)
-    #define PUB_IPv4_ADDR_SRP "%{public, network:in_addr}.4P"
-    #define PRI_IPv4_ADDR_SRP "%{private, mask.hash, network:in_addr}.4P"
-    #define IPv4_ADDR_PARAM_SRP(ADDR, BUF) ((uint8_t *)ADDR)
+#    define IPv4_ADDR_GEN_SRP(ADDR, BUF_NAME) do {} while(0)
+#    define PUB_IPv4_ADDR_SRP "%{public, network:in_addr}.4P"
+#    define PRI_IPv4_ADDR_SRP "%{private, mask.hash, network:in_addr}.4P"
+#    define IPv4_ADDR_PARAM_SRP(ADDR, BUF) ((uint8_t *)ADDR)
     // IPv6
-    #define IPv6_ADDR_GEN_SRP(ADDR, BUF_NAME) do {} while(0)
-    #define PUB_IPv6_ADDR_SRP "%{public, network:in6_addr}.16P%{public}s"
-    #define PRI_IPv6_ADDR_SRP "%{public}s%{private, mask.hash, network:in6_addr}.16P"
-    #define IPv6_ADDR_PARAM_SRP(ADDR, BUF) ADDRESS_RANGE_STR((uint8_t *)(ADDR)), ((uint8_t *)(ADDR))
+#    define IPv6_ADDR_GEN_SRP(ADDR, BUF_NAME) do {} while(0)
+#    define PUB_IPv6_ADDR_SRP "%{public, network:in6_addr}.16P%{public}s"
+#    define PRI_IPv6_ADDR_SRP "%{public}s%{private, mask.hash, network:in6_addr}.16P"
+#    define IPv6_ADDR_PARAM_SRP(ADDR, BUF) ADDRESS_RANGE_STR((uint8_t *)(ADDR)), ((uint8_t *)(ADDR))
     // Segmented IPv6
     // Subnet part can always be public.
-    #define SEGMENTED_IPv6_ADDR_GEN_SRP(ADDR, BUF_NAME) do {} while(0)
-    #define PUB_SEGMENTED_IPv6_ADDR_SRP "{%{public, srp:in6_addr_segment}.6P%{public}s, " \
+#    define SEGMENTED_IPv6_ADDR_GEN_SRP(ADDR, BUF_NAME) do {} while(0)
+#    define PUB_SEGMENTED_IPv6_ADDR_SRP "{%{public, srp:in6_addr_segment}.6P%{public}s, " \
                                             "%{public, srp:in6_addr_segment}.2P, " \
                                             "%{public, srp:in6_addr_segment}.8P}"
-    #define PRI_SEGMENTED_IPv6_ADDR_SRP "{%{public}s%{private, mask.hash, srp:in6_addr_segment}.6P:" \
+#    define PRI_SEGMENTED_IPv6_ADDR_SRP "{%{public}s%{private, mask.hash, srp:in6_addr_segment}.6P:" \
                                             "%{public, mask.hash, srp:in6_addr_segment}.2P:" \
                                             "%{private, mask.hash, srp:in6_addr_segment}.8P}"
-    #define SEGMENTED_IPv6_ADDR_PARAM_SRP(ADDR, BUF) ADDRESS_RANGE_STR((uint8_t *)(ADDR)), ((uint8_t *)(ADDR)), \
+#    define SEGMENTED_IPv6_ADDR_PARAM_SRP(ADDR, BUF) ADDRESS_RANGE_STR((uint8_t *)(ADDR)), ((uint8_t *)(ADDR)), \
                                                         ((uint8_t *)(ADDR) + 6), ((uint8_t *)(ADDR) + 8)
     // MAC address
-    #define PUB_MAC_ADDR_SRP "%{public, srp:mac_addr}.6P"
-    #define PRI_MAC_ADDR_SRP "%{private, mask.hash, srp:mac_addr}.6P"
-    #define MAC_ADDR_PARAM_SRP(ADDR) ((uint8_t *)ADDR)
+#    define PUB_MAC_ADDR_SRP "%{public, srp:mac_addr}.6P"
+#    define PRI_MAC_ADDR_SRP "%{private, mask.hash, srp:mac_addr}.6P"
+#    define MAC_ADDR_PARAM_SRP(ADDR) ((uint8_t *)ADDR)
 
 #else // ifdef OS_LOG_ENABLED
     // When os_log is not available, all logs would be public.
     // Define log specifier
     // String
-    #define PUB_S_SRP "%s"
-    #define PRI_S_SRP PUB_S_SRP
+#    define PUB_S_SRP "%s"
+#    define PRI_S_SRP PUB_S_SRP
     // DNS name, when the pointer to DNS name is NULL, <NULL> will be logged.
-    #if defined(MDNS_NO_STRICT) && (!MDNS_NO_STRICT)
-        #define SRP_LOG_STRNCPY_STRICT mdns_strlcpy
-    #else
-        #define SRP_LOG_STRNCPY_STRICT strlcpy
-    #endif
-    #ifdef IOLOOP_MACOS
-        #define SRP_LOG_STRNCPY SRP_LOG_STRNCPY_STRICT
-    #else
-        #define SRP_LOG_STRNCPY strncpy
-    #endif // IOLOOP_MACOS
-    #define DNS_NAME_GEN_SRP(NAME, BUF_NAME) \
+#    if defined(MDNS_NO_STRICT) && (!MDNS_NO_STRICT)
+#        define SRP_LOG_STRNCPY_STRICT mdns_strlcpy
+#    else
+#        define SRP_LOG_STRNCPY_STRICT strlcpy
+#    endif
+#    ifdef IOLOOP_MACOS
+#        define SRP_LOG_STRNCPY SRP_LOG_STRNCPY_STRICT
+#    else
+#        define SRP_LOG_STRNCPY strncpy
+#    endif // IOLOOP_MACOS
+#    define DNS_NAME_GEN_SRP(NAME, BUF_NAME) \
         char BUF_NAME[DNS_MAX_NAME_SIZE_ESCAPED + 1]; \
         if (NAME != NULL) { \
             dns_name_print(NAME, BUF_NAME, sizeof(BUF_NAME)); \
@@ -349,32 +353,32 @@
             SRP_LOG_STRNCPY(BUF_NAME, "<null>", \
                             sizeof("<null>") < sizeof(BUF_NAME) ? sizeof("<null>") : sizeof(BUF_NAME)); \
         }
-    #define PUB_DNS_NAME_SRP "%s"
-    #define PRI_DNS_NAME_SRP PUB_DNS_NAME_SRP
-    #define DNS_NAME_PARAM_SRP(NAME, BUF) (BUF)
+#    define PUB_DNS_NAME_SRP "%s"
+#    define PRI_DNS_NAME_SRP PUB_DNS_NAME_SRP
+#    define DNS_NAME_PARAM_SRP(NAME, BUF) (BUF)
     // IP address
     // IPv4
-    #define IPv4_ADDR_GEN_SRP(ADDR, BUF_NAME) char BUF_NAME[INET_ADDRSTRLEN]; \
+#    define IPv4_ADDR_GEN_SRP(ADDR, BUF_NAME) char BUF_NAME[INET_ADDRSTRLEN]; \
                                                     inet_ntop(AF_INET, ((uint8_t *)ADDR), BUF_NAME, sizeof(BUF_NAME))
-    #define PUB_IPv4_ADDR_SRP "%s"
-    #define PRI_IPv4_ADDR_SRP PUB_IPv4_ADDR_SRP
-    #define IPv4_ADDR_PARAM_SRP(ADDR, BUF) (BUF)
+#    define PUB_IPv4_ADDR_SRP "%s"
+#    define PRI_IPv4_ADDR_SRP PUB_IPv4_ADDR_SRP
+#    define IPv4_ADDR_PARAM_SRP(ADDR, BUF) (BUF)
     // IPv6
-    #define IPv6_ADDR_GEN_SRP(ADDR, BUF_NAME) char BUF_NAME[INET6_ADDRSTRLEN]; \
+#    define IPv6_ADDR_GEN_SRP(ADDR, BUF_NAME) char BUF_NAME[INET6_ADDRSTRLEN]; \
                                                     inet_ntop(AF_INET6, ((uint8_t *)ADDR), BUF_NAME, sizeof(BUF_NAME))
-    #define PUB_IPv6_ADDR_SRP "%s%s"
-    #define PRI_IPv6_ADDR_SRP PUB_IPv6_ADDR_SRP
-    #define IPv6_ADDR_PARAM_SRP(ADDR, BUF) (BUF), ADDRESS_RANGE_STR((uint8_t *)ADDR)
+#    define PUB_IPv6_ADDR_SRP "%s%s"
+#    define PRI_IPv6_ADDR_SRP PUB_IPv6_ADDR_SRP
+#    define IPv6_ADDR_PARAM_SRP(ADDR, BUF) (BUF), ADDRESS_RANGE_STR((uint8_t *)ADDR)
     // Segmented IPv6
-    #define SEGMENTED_IPv6_ADDR_GEN_SRP(ADDR, BUF_NAME) IPv6_ADDR_GEN_SRP(ADDR, BUF_NAME)
+#    define SEGMENTED_IPv6_ADDR_GEN_SRP(ADDR, BUF_NAME) IPv6_ADDR_GEN_SRP(ADDR, BUF_NAME)
 
-    #define PUB_SEGMENTED_IPv6_ADDR_SRP PUB_IPv6_ADDR_SRP
-    #define PRI_SEGMENTED_IPv6_ADDR_SRP PRI_IPv6_ADDR_SRP
-    #define SEGMENTED_IPv6_ADDR_PARAM_SRP(ADDR, BUF) IPv6_ADDR_PARAM_SRP(ADDR, BUF)
+#    define PUB_SEGMENTED_IPv6_ADDR_SRP PUB_IPv6_ADDR_SRP
+#    define PRI_SEGMENTED_IPv6_ADDR_SRP PRI_IPv6_ADDR_SRP
+#    define SEGMENTED_IPv6_ADDR_PARAM_SRP(ADDR, BUF) IPv6_ADDR_PARAM_SRP(ADDR, BUF)
     // MAC address
-    #define PUB_MAC_ADDR_SRP "%02x:%02x:%02x:%02x:%02x:%02x"
-    #define PRI_MAC_ADDR_SRP PUB_MAC_ADDR_SRP
-    #define MAC_ADDR_PARAM_SRP(ADDR) ((uint8_t *)ADDR)[0], ((uint8_t *)ADDR)[1], ((uint8_t *)ADDR)[2], \
+#    define PUB_MAC_ADDR_SRP "%02x:%02x:%02x:%02x:%02x:%02x"
+#    define PRI_MAC_ADDR_SRP PUB_MAC_ADDR_SRP
+#    define MAC_ADDR_PARAM_SRP(ADDR) ((uint8_t *)ADDR)[0], ((uint8_t *)ADDR)[1], ((uint8_t *)ADDR)[2], \
                                         ((uint8_t *)ADDR)[3], ((uint8_t *)ADDR)[4], ((uint8_t *)ADDR)[5]
 
 #endif // ifdef OS_LOG_ENABLED
diff --git a/ServiceRegistration/srp-mdns-proxy.c b/ServiceRegistration/srp-mdns-proxy.c
index 7288c32..f11dd74 100644
--- a/ServiceRegistration/srp-mdns-proxy.c
+++ b/ServiceRegistration/srp-mdns-proxy.c
@@ -1,6 +1,6 @@
 /* srp-mdns-proxy.c
  *
- * Copyright (c) 2019-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2019-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -53,33 +53,50 @@
 #include "ioloop-common.h"
 #include "thread-device.h"
 #include "nat64-macos.h"
+#include "srp-dnssd.h"
+#include "ifpermit.h"
+#include "state-machine.h"
+#include "thread-service.h"
+#include "omr-watcher.h"
+#include "omr-publisher.h"
+#include "service-publisher.h"
 
 
-#if SRP_FEATURE_NAT64 || STUB_ROUTER
-#include <mdns/managed_defaults.h>
-#endif
-
 #if SRP_FEATURE_NAT64
 #include "nat64.h"
 #endif
 
 
-#define ADDRESS_RECORD_TTL   120        // Address records have TTL of 120s to avoid advertising stale data for too long.
-#define OTHER_RECORD_TTL    3600        // Other records we're not so worried about.
+#ifdef SRP_TEST_SERVER
+#include "srp-test-runner.h"
+#endif
+
+#define ADDRESS_RECORD_TTL  4500
+#define OTHER_RECORD_TTL    4500
+
+#define _DNSSD_API_AVAILABLE_FALL_2024 (1)
 
 static const char local_suffix_ld[] = ".local";
 static const char *local_suffix = &local_suffix_ld[1];
 
+os_log_t global_os_log;
 void *dns_service_op_not_to_be_freed;
 srp_server_t *srp_servers;
 const uint8_t thread_anycast_preamble[7] = { 0, 0, 0, 0xff, 0xfe, 0, 0xfc };
-const uint8_t thread_rloc_preamble[6] = { 0, 0, 0, 0xff, 0xf0, 0 };
+const uint8_t thread_rloc_preamble[6] = { 0, 0, 0, 0xff, 0xfe, 0 };
+
+extern int num_push_sessions;
+extern int dp_num_outstanding_queries;
+extern int num_push_sessions_dropped_for_load;
+extern int num_queries_dropped_for_load;
 
 //======================================================================================================================
 // MARK: - Forward references
 
+static bool register_host_record(adv_host_t *host, adv_record_t *record, const bool skipping);
 static void register_host_record_completion(DNSServiceRef sdref, DNSRecordRef rref,
                                             DNSServiceFlags flags, DNSServiceErrorType error_code, void *context);
+static bool register_instance(adv_instance_t *instance);
 static void register_instance_completion(DNSServiceRef sdref, DNSServiceFlags flags, DNSServiceErrorType error_code,
                                          const char *name, const char *regtype, const char *domain, void *context);
 static void update_from_host(adv_host_t *host);
@@ -94,28 +111,33 @@
 //======================================================================================================================
 // MARK: - Functions
 
-static void
-remove_shared_record(srp_server_t *server_state, adv_record_t *record)
+void
+srp_mdns_shared_record_remove(srp_server_t *server_state, adv_record_t *record)
 {
     RETAIN_HERE(record, adv_record);
-    if (record->rref != NULL && record->shared_txn != 0 &&
-        record->shared_txn == (intptr_t)server_state->shared_registration_txn)
-    {
-        int err = DNSServiceRemoveRecord(server_state->shared_registration_txn->sdref, record->rref, 0);
-        // We can't release the record here if we got an error removing it, because if we get an error removing it,
-        // it doesn't get removed from the list. This should never happen, but if it does, the record will leak.
-        if (err == kDNSServiceErr_NoError) {
-            RELEASE_HERE(record, adv_record); // Release the DNSService callback's reference
-        } else {
-            // At this point we should never see an error calling DNSServiceRemoveRecord, so if we do, call
-            // attention to it.
-            if (!record->update_pending) {
-                FAULT("DNSServiceRemoveRecord(%p, %p, %p, 0) returned %d",
-                      server_state->shared_registration_txn->sdref, record, record->rref, err);
+    if (record->rref != NULL) {
+        if (record->shared_txn != 0 && record->shared_txn == (intptr_t)server_state->shared_registration_txn) {
+            INFO("removing rref %p", record->rref);
+            int err = dns_service_remove_record(server_state,
+                                                server_state->shared_registration_txn->sdref, record->rref, 0);
+            // We can't release the record here if we got an error removing it, because if we get an error removing it,
+            // it doesn't get removed from the list. This should never happen, but if it does, the record will leak.
+            if (err == kDNSServiceErr_NoError) {
+                RELEASE_HERE(record, adv_record); // Release the DNSService callback's reference
+            } else {
+                // At this point we should never see an error calling DNSServiceRemoveRecord, so if we do, call
+                // attention to it.
+                if (!record->update_pending) {
+                    FAULT("DNSServiceRemoveRecord(%p, %p, %p, 0) returned %d",
+                          server_state->shared_registration_txn->sdref, record, record->rref, err);
+                }
             }
+        } else {
+            INFO("didn't remove stale rref %p because %lx != %p",
+                 record->rref, record->shared_txn, server_state->shared_registration_txn);
         }
+        record->rref = NULL;
     }
-    record->rref = NULL;
     record->shared_txn = 0;
     RELEASE_HERE(record, adv_record);
 }
@@ -132,6 +154,7 @@
     if (record->host != NULL) {
         RELEASE_HERE(record->host, adv_host);
     }
+
     free(record->rdata);
     free(record);
 }
@@ -140,7 +163,7 @@
 adv_instance_finalize(adv_instance_t *instance)
 {
     if (instance->txn != NULL) {
-        ioloop_dnssd_txn_cancel(instance->txn);
+        ioloop_dnssd_txn_cancel_srp(instance->host->server_state, instance->txn);
         ioloop_dnssd_txn_release(instance->txn);
     }
     if (instance->txt_data != NULL) {
@@ -164,16 +187,44 @@
         RELEASE_HERE(instance->update, adv_update);
         instance->update = NULL;
     }
+    if (instance->retry_wakeup != NULL) {
+        ioloop_wakeup_release(instance->retry_wakeup);
+        instance->retry_wakeup = NULL;
+    }
     free(instance);
 }
 
-static void
+void
 adv_instance_context_release(void *NONNULL context)
 {
     adv_instance_t *instance = context;
     RELEASE_HERE(instance, adv_instance);
 }
 
+void
+adv_instance_retain_(adv_instance_t *instance, const char *file, int line)
+{
+    RETAIN(instance, adv_instance);
+}
+
+void
+adv_instance_release_(adv_instance_t *instance, const char *file, int line)
+{
+    RELEASE(instance, adv_instance);
+}
+
+void
+adv_record_retain_(adv_record_t *record, const char *file, int line)
+{
+    RETAIN(record, adv_record);
+}
+
+void
+adv_record_release_(adv_record_t *record, const char *file, int line)
+{
+    RELEASE(record, adv_record);
+}
+
 #define DECLARE_VEC_CREATE(type)                        \
 static type ## _vec_t *                                 \
 type ## _vec_create(int size)                           \
@@ -240,11 +291,202 @@
 DECLARE_VEC_COPY(adv_record);
 DECLARE_VEC_FINALIZE(adv_record);
 
+static void
+srp_dump_server_stats(srp_server_t *server_state, bool full, bool periodic)
+{
+    // For testing, emit a count of how many hosts, services and address records there are
+    int host_count = 0;
+    int a_record_count = 0;
+    int aaaa_record_count = 0;
+    int instance_count = 0;
+    int matter_host_count = 0;
+    int hap_host_count = 0;
+    int64_t now = ioloop_timenow();
+    static int last_num_push_sessions;
+    static int last_dp_num_outstanding_queries;
+    static int last_num_push_sessions_dropped_for_load;
+    static int last_num_queries_dropped_for_load;
+
+    for (adv_host_t *hp = server_state->hosts; hp != NULL; hp = hp->next) {
+        if (hp->removed) {
+            continue;
+        }
+        host_count++;
+        int expiry;
+        if (hp->lease_expiry < now) {
+            expiry = -1;
+        } else {
+            expiry = (int)((hp->lease_expiry - now) / 1000); // This should never be >MAXINT
+        }
+        if (full) {
+            INFO("host " PRI_S_SRP " key_id %xu stable %" PRIx64 " lease %d key_lease %d expiry %d" PUB_S_SRP PUB_S_SRP,
+                 hp->name, hp->key_id, hp->server_stable_id, hp->lease_interval, hp->key_lease, expiry,
+                 hp->removed ? " removed" : "", hp->update_pending ? " update-pending" : "");
+        }
+        if (hp->addresses != NULL) {
+            for (int i = 0; i < hp->addresses->num; i++) {
+                if (hp->addresses->vec[i] != NULL) {
+                    adv_record_t *record = hp->addresses->vec[i];
+                    if (record->rrtype == dns_rrtype_a) {
+                        if (full) {
+                            IPv4_ADDR_GEN_SRP(record->rdata, addr_buf);
+                            INFO("  IN    A " PRI_IPv4_ADDR_SRP PRI_S_SRP, IPv4_ADDR_PARAM_SRP(record->rdata, addr_buf),
+                                 record->shared_txn == (intptr_t)server_state->shared_registration_txn ? " live" : "");
+                        }
+                        a_record_count++;
+                    } else if (record->rrtype == dns_rrtype_aaaa) {
+                        if (full) {
+                            IPv6_ADDR_GEN_SRP((const uint8_t *)record->rdata, addr_buf);
+                            INFO("  IN AAAA " PRI_IPv6_ADDR_SRP PRI_S_SRP, IPv6_ADDR_PARAM_SRP(record->rdata, addr_buf),
+                                 record->shared_txn == (intptr_t)server_state->shared_registration_txn ? " live" : "");
+                        }
+                        aaaa_record_count++;
+                    }
+                }
+            }
+        }
+        bool matter_instance_present = false, hap_instance_present = false;
+        if (hp->instances != NULL) {
+            for (int i = 0; i < hp->instances->num; i++) {
+                adv_instance_t *instance = hp->instances->vec[i];
+                if (instance != NULL) {
+                    if (full) {
+                        char txt_buf[DNS_DATA_SIZE];
+                        if (instance->txt_data != NULL) {
+                            dns_txt_data_print(txt_buf, DNS_DATA_SIZE, instance->txt_length, instance->txt_data);
+                        } else {
+                            txt_buf[0] = 0;
+                        }
+                        const char *status = "removed";
+                        if (!instance->removed) {
+                            if (instance->txn == NULL) {
+                                status = "unregistered";
+                            } else if (instance->shared_txn != (intptr_t)server_state->shared_registration_txn) {
+                                status = "stale";
+                            } else {
+                                status = "live";
+                            }
+                        }
+                        INFO("  " PUB_S_SRP " instance " PRI_S_SRP " " PRI_S_SRP " %d (" PRI_S_SRP ")",
+                             status, instance->instance_name, instance->service_type, instance->port, txt_buf);
+                    }
+                    if (!instance->removed) {
+                        instance_count++;
+                        if (instance->service_type != NULL) {
+                            const char matter_prefix[] = "_matter";
+                            const char hap_prefix[] = "_hap._udp";
+                            if (!strncmp(instance->service_type, matter_prefix, sizeof(matter_prefix) - 1)) {
+                                matter_instance_present = true;
+                            } else if (!strncmp(instance->service_type, hap_prefix, sizeof(hap_prefix) - 1)) {
+                                hap_instance_present = true;
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        if (matter_instance_present) {
+            matter_host_count++;
+        } else if (hap_instance_present) { // If both, only count matter.
+            hap_host_count++;
+        }
+    }
+    INFO(PUB_S_SRP "%d hosts (%d matter, %d hap), %d instances, %d a records, %d aaaa records at %.6lf",
+         periodic ? "" : "after update, ", host_count, matter_host_count, hap_host_count, instance_count, a_record_count,
+         aaaa_record_count, srp_fractional_time());
+
+#if STUB_ROUTER
+    route_state_t *route_state = server_state->route_state;
+    if (route_state) {
+        SEGMENTED_IPv6_ADDR_GEN_SRP(&route_state->srp_listener_ip_address, addr_buf);
+        // do we have an SRP listener?
+        if (route_state->srp_listener != NULL) {
+            INFO("have SRP listener on " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d",
+                 SEGMENTED_IPv6_ADDR_PARAM_SRP(&route_state->srp_listener_ip_address, addr_buf),
+                 route_state->srp_service_listen_port);
+        } else {
+            INFO("no SRP listener");
+        }
+
+        // are we publishing anycast services?
+        INFO(PUB_S_SRP "advertising anycast service", route_state->advertising_srp_anycast_service ? "" : "not ");
+
+        // are we publishing unicast service?
+        if (route_state->advertising_srp_unicast_service) {
+            INFO("advertising unicast service on " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d",
+                 SEGMENTED_IPv6_ADDR_PARAM_SRP(&route_state->srp_listener_ip_address, addr_buf),
+                 route_state->srp_service_listen_port);
+        } else {
+            INFO("not advertising unicast service");
+        }
+
+        // what SRP replication peers do we see? and how many are we actively connected to?
+        srpl_dump_connection_states(server_state);
+
+        // are we publishing OMR prefix?
+        if (route_state->omr_publisher != NULL &&
+            omr_publisher_publishing_prefix(route_state->omr_publisher))
+        {
+            omr_prefix_t *prefix = omr_publisher_published_prefix_get(route_state->omr_publisher);
+            SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf);
+            INFO("publishing " PUB_S_SRP " OMR prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d",
+                 omr_publisher_publishing_dhcp(route_state->omr_publisher) ? "dhcp" : "ula",
+                 SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf),
+                 prefix->prefix_length);
+        } else {
+            INFO("not publishing OMR prefix");
+        }
+
+        // what prefixes do we see on Thread?
+        if (route_state->omr_watcher != NULL) {
+            omr_prefix_t *thread_prefixes = omr_watcher_prefixes_get(route_state->omr_watcher);
+            for (struct omr_prefix *prefix = thread_prefixes; prefix != NULL; prefix = prefix->next) {
+                SEGMENTED_IPv6_ADDR_GEN_SRP(prefix->prefix.s6_addr, prefix_buf);
+                INFO("OMR prefix " PRI_SEGMENTED_IPv6_ADDR_SRP "/%d seen on thread" PUB_S_SRP PUB_S_SRP
+                     PUB_S_SRP, SEGMENTED_IPv6_ADDR_PARAM_SRP(prefix->prefix.s6_addr, prefix_buf),
+                     prefix->prefix_length, prefix->user ? " (user)" : "", prefix->ncp ? " (ncp)": "",
+                     prefix->stable ? " (stable)" : "");
+            }
+        }
+
+        // are we publishing infrastructure prefix?
+        interface_t *interface;
+        bool is_advertising = false;
+        for (interface = route_state->interfaces; interface; interface = interface->next) {
+            if (interface->our_prefix_advertised) {
+               SEGMENTED_IPv6_ADDR_GEN_SRP(interface->ipv6_prefix.s6_addr, ipv6_prefix_buf);
+               INFO("advertising infrastructure prefix " PRI_SEGMENTED_IPv6_ADDR_SRP " on " PUB_S_SRP,
+                    SEGMENTED_IPv6_ADDR_PARAM_SRP(interface->ipv6_prefix.s6_addr, ipv6_prefix_buf),
+                    interface->name);
+               is_advertising = true;
+            }
+        }
+        if (!is_advertising) {
+            INFO("not advertising infrastructure prefix");
+        }
+    }
+#endif // STUB_ROUTER
+
+    // how many DNS push queries added since last state dump?
+    // how many DNS queries seen since last state dump?
+    // how many DNS queries dropped for load?
+    // how many DNS Push connections dropped for load?
+    INFO("%d push sessions and %d queries added, %d push sessions and %d queries dropped for load",
+         num_push_sessions - last_num_push_sessions,
+         dp_num_outstanding_queries - last_dp_num_outstanding_queries,
+         num_push_sessions_dropped_for_load - last_num_push_sessions_dropped_for_load,
+         num_queries_dropped_for_load - last_num_queries_dropped_for_load);
+    last_num_push_sessions = num_push_sessions;
+    last_dp_num_outstanding_queries = dp_num_outstanding_queries;
+    last_num_push_sessions_dropped_for_load = num_push_sessions_dropped_for_load;
+    last_num_queries_dropped_for_load = num_queries_dropped_for_load;
+}
+
 // We call advertise_finished when a client request has finished, successfully or otherwise.
 #if SRP_FEATURE_REPLICATION
 static bool
 srp_replication_advertise_finished(adv_host_t *host, char *hostname, srp_server_t *server_state,
-                                   srpl_connection_t *srpl_connection, comm_t *connection, int rcode)
+                                   srpl_connection_t *srpl_connection, comm_t *connection, int rcode, bool last)
 {
 	if (server_state->srp_replication_enabled) {
         INFO("hostname = " PRI_S_SRP "  host = %p  server_state = %p  srpl_connection = %p  connection = %p  rcode = "
@@ -255,7 +497,14 @@
             INFO("replication advertise finished: host " PRI_S_SRP ": rcode = " PUB_S_SRP,
                  hostname, dns_rcode_name(rcode));
             if (srpl_connection != NULL) {
-                srpl_advertise_finished_event_send(hostname, rcode, server_state);
+                if (last) {
+                    srpl_advertise_finished_event_send(hostname, rcode, server_state);
+#ifdef SRP_TEST_SERVER
+                    if (srpl_connection->srpl_advertise_finished_callback != NULL) {
+                        srpl_connection->srpl_advertise_finished_callback(srpl_connection);
+                    }
+#endif
+                }
 
                 if (host != NULL && host->srpl_connection != NULL) {
                     if (rcode == dns_rcode_noerror) {
@@ -301,22 +550,50 @@
 }
 #endif // SRP_FEATURE_REPLICATION
 
+static void
+srp_ml_eid_mapping_callback(void *context, cti_status_t status)
+{
+    adv_record_t *arec = context;
+    adv_host_t *host = arec->host;
+    SEGMENTED_IPv6_ADDR_GEN_SRP(arec->rdata, omr_buf);
+    if (status == kCTIStatus_NoError) {
+        if (host == NULL) {
+            INFO("mapping for address " PRI_SEGMENTED_IPv6_ADDR_SRP " was orphaned.",
+                 SEGMENTED_IPv6_ADDR_PARAM_SRP(arec->rdata, omr_buf));
+        } else {
+            INFO("mapping for address " PRI_SEGMENTED_IPv6_ADDR_SRP " to host " PRI_S_SRP " succeeded",
+                 SEGMENTED_IPv6_ADDR_PARAM_SRP(arec->rdata, omr_buf), host->name);
+        }
+    } else {
+        if (host == NULL) {
+            INFO("orphaned mapping for address " PRI_SEGMENTED_IPv6_ADDR_SRP " failed: %d",
+                 SEGMENTED_IPv6_ADDR_PARAM_SRP(arec->rdata, omr_buf), status);
+        } else {
+            INFO("mapping for address " PRI_SEGMENTED_IPv6_ADDR_SRP " to host " PRI_S_SRP " failed: %d",
+                 SEGMENTED_IPv6_ADDR_PARAM_SRP(arec->rdata, omr_buf), host->name, status);
+        }
+    }
+    RELEASE_HERE(arec, adv_record);
+}
+
 // We call advertise_finished when a client request has finished, successfully or otherwise.
 static void
 advertise_finished(adv_host_t *host, char *hostname, srp_server_t *server_state, srpl_connection_t *srpl_connection,
-                   comm_t *connection, message_t *message, int rcode, client_update_t *client, bool send_response)
+                   comm_t *connection, message_t *message, int rcode, client_update_t *client, bool send_response,
+                   bool last)
 {
     struct iovec iov;
     dns_wire_t response;
 
 #if SRP_FEATURE_REPLICATION
-    if (srp_replication_advertise_finished(host, hostname, server_state, srpl_connection, connection, rcode)) {
+    if (srp_replication_advertise_finished(host, hostname, server_state, srpl_connection, connection, rcode, last)) {
         return;
     }
 #else
     (void)host;
     (void)server_state;
     (void)srpl_connection;
+    (void)last;
 #endif // SRP_FEATURE_REPLICATION
     INFO("host " PRI_S_SRP ": rcode = " PUB_S_SRP ", lease = %d, key_lease = %d  connection = %p", hostname, dns_rcode_name(rcode),
          client ? client->host_lease : 0, client ? client->key_lease : 0, connection);
@@ -357,12 +634,6 @@
         dns_u32_to_wire(&towire, client->host_lease);    // LEASE (e.g. 1 hour)
         dns_u32_to_wire(&towire, client->key_lease);     // KEY-LEASE (7 days)
         dns_edns0_option_end(&towire);                   // Now we know OPTION-LENGTH
-        if (!client->serial_sent) {
-            dns_u16_to_wire(&towire, dns_opt_srp_serial);    // OPTION-CODE
-            dns_edns0_option_begin(&towire);                 // OPTION-LENGTH
-            dns_u32_to_wire(&towire, client->serial_number); // LEASE (e.g. 1 hour)
-            dns_edns0_option_end(&towire);                   // Now we know OPTION-LENGTH
-        }
         dns_rdlength_end(&towire);
         // It should not be possible for this to happen; if it does, the client
         // might not renew its lease in a timely manner.
@@ -390,6 +661,13 @@
 }
 
 static void
+srp_adv_host_context_release(void *context)
+{
+    adv_host_t *host = context;
+    RELEASE_HERE(host, adv_host);
+}
+
+static void
 wait_retry(adv_host_t *host)
 {
     int64_t now = ioloop_timenow();
@@ -408,11 +686,26 @@
         host->retry_interval *= 2;
     }
     INFO("waiting %d seconds...", host->retry_interval);
-    ioloop_add_wake_event(host->retry_wakeup, host, retry_callback, NULL, host->retry_interval * 1000);
+    ioloop_add_wake_event(host->retry_wakeup, host, retry_callback, srp_adv_host_context_release, host->retry_interval * 1000);
+    RETAIN_HERE(host, adv_host);
 }
 
-static bool
-setup_shared_registration_txn(srp_server_t *server_state)
+static void
+shared_registration_fail(void *context, int UNUSED status)
+{
+    srp_server_t *server_state = context;
+    dnssd_txn_t *txn = server_state->shared_registration_txn;
+    DNSServiceRef sdref = txn == NULL ? NULL : txn->sdref;
+    INFO("shared registration failed: txn %p sdref %p", server_state->shared_registration_txn, sdref);
+    if (txn != NULL) {
+        ioloop_dnssd_txn_cancel(txn);
+        ioloop_dnssd_txn_release(txn);
+        server_state->shared_registration_txn = NULL;
+    }
+}
+
+bool
+srp_mdns_shared_registration_txn_setup(srp_server_t *server_state)
 {
     if (server_state->shared_registration_txn == NULL) {
         DNSServiceRef sdref;
@@ -420,7 +713,7 @@
         if (err != kDNSServiceErr_NoError) {
             return false;
         }
-        server_state->shared_registration_txn = ioloop_dnssd_txn_add(sdref, NULL, NULL, NULL);
+        server_state->shared_registration_txn = ioloop_dnssd_txn_add(sdref, server_state, NULL, shared_registration_fail);
         if (server_state->shared_registration_txn == NULL) {
             ERROR("unable to create shared connection for registration.");
             dns_service_op_not_to_be_freed = NULL;
@@ -526,22 +819,6 @@
 }
 
 static void
-client_free(client_update_t *client)
-{
-    srp_update_free_parts(client->instances, NULL, client->services, client->removes, client->host);
-    if (client->parsed_message != NULL) {
-        dns_message_free(client->parsed_message);
-    }
-    if (client->message != NULL) {
-        ioloop_message_release(client->message);
-    }
-    if (client->connection != NULL) {
-        ioloop_comm_release(client->connection);
-    }
-    free(client);
-}
-
-static void
 adv_record_vec_remove_update(adv_record_vec_t *vec, adv_update_t *update)
 {
     for (int i = 0; i < vec->num; i++) {
@@ -568,10 +845,17 @@
 {
     for (int i = 0; i < instances->num; i++) {
         adv_instance_t *instance = instances->vec[i];
-        if (instance != NULL && instance->txn != NULL) {
-            ioloop_dnssd_txn_cancel(instance->txn);
-            ioloop_dnssd_txn_release(instance->txn);
-            instance->txn = NULL;
+        if (instance != NULL) {
+            if (instance->txn != NULL) {
+                ioloop_dnssd_txn_cancel_srp(instance->host->server_state, instance->txn);
+                ioloop_dnssd_txn_release(instance->txn);
+                instance->txn = NULL;
+            }
+            if (instance->retry_wakeup != NULL) {
+                ioloop_cancel_wake_event(instance->retry_wakeup);
+                ioloop_wakeup_release(instance->retry_wakeup);
+                instance->retry_wakeup = NULL;
+            }
         }
     }
 }
@@ -611,7 +895,7 @@
     }
 
     if (update->client != NULL) {
-        client_free(update->client);
+        srp_parse_client_updates_free(update->client);
         update->client = NULL;
     }
 
@@ -680,7 +964,7 @@
                     }
                 } else {
                     if (record->rref != NULL) {
-                        remove_shared_record(host->server_state, record);
+                        srp_mdns_shared_record_remove(host->server_state, record);
                     }
                 }
             }
@@ -709,7 +993,7 @@
                     faulted = true;
                 }
             } else {
-                remove_shared_record(host->server_state, update->key);
+                srp_mdns_shared_record_remove(host->server_state, update->key);
             }
         }
         RELEASE_HERE(update->key, adv_record);
@@ -734,8 +1018,8 @@
         client_update_t *client = update->client;
         adv_update_cancel(update);
         advertise_finished(host, host->name, host->server_state, host->srpl_connection,
-                           client->connection, client->message, rcode, NULL, send_response);
-        client_free(client);
+                           client->connection, client->message, rcode, NULL, send_response, true);
+        srp_parse_client_updates_free(client);
         update->client = NULL;
         // If we don't have a lease yet, or the old lease has expired, remove the host.
         // However, if the expire flag is false, it's because we're already finalizing the
@@ -762,7 +1046,7 @@
         for (i = 0; i < host->addresses->num; i++) {
             if (host->addresses->vec[i] != NULL) {
                 INFO("Removing AAAA record for " PRI_S_SRP, host->registered_name);
-                remove_shared_record(host->server_state, host->addresses->vec[i]);
+                srp_mdns_shared_record_remove(host->server_state, host->addresses->vec[i]);
                 RELEASE_HERE(host->addresses->vec[i], adv_record);
                 host->addresses->vec[i] = NULL;
             }
@@ -775,27 +1059,20 @@
 static void
 host_invalidate(adv_host_t *host)
 {
-    int i;
-
     // Get rid of the retry wake event.
     if (host->retry_wakeup != NULL) {
         ioloop_cancel_wake_event(host->retry_wakeup);
     }
+    if (host->re_register_wakeup != NULL) {
+        ioloop_cancel_wake_event(host->re_register_wakeup);
+    }
 
     // Remove the address records.
     host_addr_free(host);
 
     // Remove the services.
     if (host->instances != NULL) {
-        for (i = 0; i < host->instances->num; i++) {
-            if (host->instances->vec[i] != NULL) {
-                if (host->instances->vec[i] != NULL && host->instances->vec[i]->txn != NULL) {
-                    ioloop_dnssd_txn_cancel(host->instances->vec[i]->txn);
-                    ioloop_dnssd_txn_release(host->instances->vec[i]->txn);
-                    host->instances->vec[i]->txn = NULL;
-                }
-            }
-        }
+        adv_instances_cancel(host->instances);
         RELEASE_HERE(host->instances, adv_instance_vec);
         host->instances = NULL;
     }
@@ -805,7 +1082,7 @@
         host->update = NULL;
     }
     if (host->key_record != NULL) {
-        remove_shared_record(host->server_state, host->key_record);
+        srp_mdns_shared_record_remove(host->server_state, host->key_record);
         RELEASE_HERE(host->key_record, adv_record);
         host->key_record = NULL;
     }
@@ -826,7 +1103,6 @@
         host->addresses = NULL;
     }
 
-
     if (host->key_rdata != NULL) {
         free(host->key_rdata);
         host->key_rdata = NULL;
@@ -845,14 +1121,20 @@
     if (host->lease_wakeup != NULL) {
         ioloop_cancel_wake_event(host->lease_wakeup);
         ioloop_wakeup_release(host->lease_wakeup);
+        host->lease_wakeup = NULL; // this will make us crash if we use it after free
     }
     // Get rid of the retry wake event.
     if (host->retry_wakeup != NULL) {
         ioloop_cancel_wake_event(host->retry_wakeup);
         ioloop_wakeup_release(host->retry_wakeup);
+        host->retry_wakeup = NULL;
     }
 
-
+    if (host->re_register_wakeup != NULL) {
+        ioloop_cancel_wake_event(host->re_register_wakeup);
+        ioloop_wakeup_release(host->re_register_wakeup);
+        host->re_register_wakeup = NULL;
+    }
     INFO("removed " PRI_S_SRP ", key_id %x", host->name ? host->name : "<null>", host->key_id);
 
     // In the default case, host->name and host->registered_name point to the same memory: we don't want a double free.
@@ -966,7 +1248,8 @@
     // update fails.  So postpone the removal for a bit.
     if (host->update != NULL) {
         INFO("reached with pending updates on host " PRI_S_SRP ".", host->registered_name);
-        ioloop_add_wake_event(host->lease_wakeup, host, lease_callback, NULL, 10 * 1000);
+        ioloop_add_wake_event(host->lease_wakeup, host, lease_callback, srp_adv_host_context_release, 10 * 1000);
+        RETAIN_HERE(host, adv_host);
         host->lease_expiry = ioloop_timenow() + 10 * 1000; // ten seconds
         return NULL;
     }
@@ -1050,7 +1333,8 @@
         when = INT32_MAX;
     }
 
-    ioloop_add_wake_event(host->lease_wakeup, host, lease_callback, NULL, (uint32_t)when);
+    ioloop_add_wake_event(host->lease_wakeup, host, lease_callback, srp_adv_host_context_release, (uint32_t)when);
+    RETAIN_HERE(host, adv_host);
 }
 
 // Called when we definitely want to make all the advertisements associated with a host go away.
@@ -1059,6 +1343,7 @@
 {
     adv_host_t **p_hosts, *host = context;
 
+
     p_hosts = host_ready(host);
     if (p_hosts == NULL) {
         return;
@@ -1077,7 +1362,7 @@
 // we know which instances /were/ updated by this particular message. instance->recent_message is a copy of the pointer
 // to the message that most recently updated this instance. When we set instance->recent_message, we don't yet know
 // if the update is going to succeed; if it fails, we can't have changed update->message. If it succeeds, then when we
-// get down to update_finished, we can compare the message that did the update to instance->recent_message; if they
+// get down to srp_mdns_update_finished, we can compare the message that did the update to instance->recent_message; if they
 // are the same, then we set the message on the instance.
 // Note that we only set instance->recent_message during register_instance_completion, so there's no timing race that
 // could happen as a result of receiving a second update to the same instance before the first has been processed.
@@ -1094,8 +1379,8 @@
     }
 }
 
-static void
-update_finished(adv_update_t *update)
+void
+srp_mdns_update_finished(adv_update_t *update)
 {
     adv_host_t *host = update->host;
     client_update_t *client = update->client;
@@ -1109,6 +1394,8 @@
     int num_host_instances = 0;
     int num_add_instances = 0;
     message_t *message = NULL;
+    client_update_t *remaining_updates = NULL;
+    srp_server_t *server_state = host->server_state;
 
     // Get the message that produced the update, if any
     if (client != NULL) {
@@ -1156,7 +1443,7 @@
                         INFO("retaining " PRI_SEGMENTED_IPv6_ADDR_SRP "on host " PRI_S_SRP,
                              SEGMENTED_IPv6_ADDR_PARAM_SRP(rdp, rdp_buf), host->registered_name);
                     } else {
-                        IPv4_ADDR_GEN_SRP(rec->rdata, addr_buf);
+                        IPv4_ADDR_GEN_SRP(rdp, rdp_buf);
                         INFO("retaining " PRI_IPv4_ADDR_SRP "on host " PRI_S_SRP,
                              IPv4_ADDR_PARAM_SRP(rdp, rdp_buf), host->registered_name);
                     }
@@ -1178,7 +1465,7 @@
                         INFO("adding " PRI_SEGMENTED_IPv6_ADDR_SRP "to host " PRI_S_SRP,
                              SEGMENTED_IPv6_ADDR_PARAM_SRP(rdp, rdp_buf), host->registered_name);
                     } else {
-                        IPv4_ADDR_GEN_SRP(rec->rdata, addr_buf);
+                        IPv4_ADDR_GEN_SRP(rdp, rdp_buf);
                         INFO("adding " PRI_IPv4_ADDR_SRP "to host " PRI_S_SRP,
                              IPv4_ADDR_PARAM_SRP(rdp, rdp_buf), host->registered_name);
                     }
@@ -1277,15 +1564,15 @@
                         ERROR("instance " PRI_S_SRP "." PRI_S_SRP " for host " PRI_S_SRP " has no connection.",
                               instance->instance_name, instance->service_type, host->name);
                     } else {
-                        ioloop_dnssd_txn_cancel(instance->txn);
+                        ioloop_dnssd_txn_cancel_srp(host->server_state, instance->txn);
                         ioloop_dnssd_txn_release(instance->txn);
                         instance->txn = NULL;
                     }
                 } else {
                     if (host->instances->vec[i] != NULL) {
                         adv_instance_t *instance = host->instances->vec[i];
-                        INFO("kept instance " PRI_S_SRP " " PRI_S_SRP " %d",
-                             instance->instance_name, instance->service_type, instance->port);
+                        INFO("kept instance " PRI_S_SRP " " PRI_S_SRP " %d, instance->message = %p",
+                             instance->instance_name, instance->service_type, instance->port, instance->message);
                         instances->vec[j] = instance;
                         RETAIN_HERE(instances->vec[j], adv_instance);
                         j++;
@@ -1335,6 +1622,12 @@
         }
     }
     instances->num = j;
+    // Clear "skip update" flag on instances.
+    for (i = 0; i < instances->num; i++) {
+        if (instances->vec[i] != NULL) {
+            instances->vec[i]->skip_update = false;
+        }
+    }
 
     // At this point we can safely modify the host object because we aren't doing any more
     // allocations.
@@ -1358,7 +1651,7 @@
     host->instances = instances;
 
     if (host->key_record != NULL && update->key != NULL && host->key_record != update->key) {
-        remove_shared_record(host->server_state, host->key_record);
+        srp_mdns_shared_record_remove(host->server_state, host->key_record);
         RELEASE_HERE(host->key_record, adv_record);
         host->key_record = NULL;
     }
@@ -1376,7 +1669,7 @@
         for (i = 0; i < update->remove_addresses->num; i++) {
             adv_record_t *record = update->remove_addresses->vec[i];
             if (record != NULL) {
-                remove_shared_record(host->server_state, record);
+                srp_mdns_shared_record_remove(host->server_state, record);
             }
         }
     }
@@ -1384,38 +1677,17 @@
     time_t lease_offset = 0;
 
     if (client) {
-        // If this is an update from a client, do the serial number processing.
-        if (client->serial_sent) {
-            INFO("host " PRI_S_SRP " serial number %" PRIu32 "->%" PRIu32 " (from client)",
-                 host->name, host->serial_number, client->serial_number);
-            host->serial_number = client->serial_number;
-            host->have_serial_number = true;
-        } else {
-            // When the client doesn't know its serial number, and we have a recorded serial number, we want to make up a new
-            // serial number that's enough ahead of the old one that it's unlikely there's a higher number elsewhere from recent
-            // communications between the client and a server we're not currently able to reach.
-            if (host->have_serial_number) {
-                INFO("host " PRI_S_SRP " serial number %" PRIu32 "->%" PRIu32 " (from history)",
-                     host->name, host->serial_number, host->serial_number + 50);
-                client->serial_number = host->serial_number + 50;
-                host->have_serial_number = true;
-            } else {
-                host->serial_number = (uint32_t)time(NULL);
-                client->serial_number = host->serial_number;
-                INFO("host " PRI_S_SRP " serial number NONE->%" PRIu32 " (from time)",
-                     host->name, client->serial_number);
-                host->have_serial_number = true;
-            }
-        }
-
         if (host->message != NULL) {
             ioloop_message_release(host->message);
         }
         host->message = client->message;
         ioloop_message_retain(host->message);
         advertise_finished(host, host->name, host->server_state, host->srpl_connection,
-                           client->connection, client->message, dns_rcode_noerror, client, true);
-        client_free(client);
+                           client->connection, client->message, dns_rcode_noerror, client, true,
+                           client->next == NULL);
+        remaining_updates = client->next;
+        client->next = NULL;
+        srp_parse_client_updates_free(client);
         update->client = NULL;
         if (host->message->received_time != 0) {
             host->update_time = host->message->received_time;
@@ -1426,13 +1698,6 @@
             INFO("setting host update time based on current time: %ld", host->message->received_time);
             host->update_time = srp_time();
         }
-
-        // It would probably be harmless to set this for replications, since the value currently wouldn't change,
-        // but to avoid future issues we only set this if it's a direct SRP update and not a replicated update.
-        if (host->message->lease == 0) {
-            host->message->lease = host->lease_interval;
-            host->message->key_lease = host->key_lease;
-        }
     } else {
         INFO("lease offset = %ld", lease_offset);
         lease_offset = srp_time() - host->update_time;
@@ -1453,8 +1718,31 @@
     // Set the lease time based on this update. Even if we scheduled an update for the next time we
     // enter the dispatch loop, we still want to schedule a lease expiry here, because it's possible
     // that in the process of returning to the dispatch loop, the scheduled update will be removed.
-    host->lease_interval = update->host_lease;
-    host->key_lease = update->key_lease;
+    if (0) {
+#if STUB_ROUTER
+    } else if (server_state->stub_router_enabled) {
+        host->lease_interval = update->host_lease;
+        host->key_lease = update->key_lease;
+#endif
+    } else {
+        // For the Thread in Mobile use case, use the duration of the key lease to determine when to expire host
+        // entries, rather than expiring them when the host lease expires. This is technically out of spec, but
+        // accomplishes part of the stated goal of keeping usable cached data around for use immediately after
+        // connecting to a Thread mesh.
+        host->lease_interval = update->key_lease;
+        host->key_lease = update->key_lease;
+    }
+
+    // It would probably be harmless to set this for replications, since the value currently wouldn't change,
+    // but to avoid future issues we only set this if it's a direct SRP update and not a replicated update.
+    // We know it's a direct SRP update because host->message->lease is zero. It would not be zero if we
+    // had received this as an SRP update, but is always zero when received directly via UDP.
+    INFO("host->message->lease = %d, host->lease_interval = %d, host->key_lease = %d",
+         host->message->lease, host->lease_interval, host->key_lease);
+    if (host->message->lease == 0) {
+        host->message->lease = host->lease_interval;
+        host->message->key_lease = host->key_lease;
+    }
 
     // We want the lease expiry event to fire the next time the lease on any instance expires, or
     // at the time the lease for the current update would expire, whichever is sooner.
@@ -1558,7 +1846,8 @@
     } else {
         INFO("scheduling wakeup to lease_callback in %" PRIu64 " for host " PRI_S_SRP,
              when / 1000, host->name);
-        ioloop_add_wake_event(host->lease_wakeup, host, lease_callback, NULL, (uint32_t)when);
+        ioloop_add_wake_event(host->lease_wakeup, host, lease_callback, srp_adv_host_context_release, (uint32_t)when);
+        RETAIN_HERE(host, adv_host);
     }
 
     // Instance vectors can hold circular references to the update object, which won't get freed until we call
@@ -1568,6 +1857,13 @@
 
     // This is letting go of the reference we retained earlier in this function, not some outstanding reference retained elsewhere.
     RELEASE_HERE(update, adv_update);
+
+    // If we were processing an SRP update, we may have additional updates to do. Start the next one now if so.
+    if (remaining_updates != NULL) {
+        srp_update_start(remaining_updates);
+    } else {
+        srp_dump_server_stats(server_state, false, false);
+    }
 }
 
 #ifdef USE_DNSSERVICE_QUEUING
@@ -1592,6 +1888,98 @@
 }
 #endif // USE_DNSSERVICE_QUEUING
 
+#define GENERATE_WAKEUP(ptr)                  \
+    if ((*ptr) == NULL) {                     \
+        (*ptr) = ioloop_wakeup_create();      \
+    }                                         \
+    if ((*ptr) == NULL) {                     \
+        ERROR("unable to make wakeup " #ptr); \
+    } else
+
+static void
+srp_instance_retry_callback(void *context)
+{
+    adv_instance_t *instance = context;
+    adv_host_t *host = instance->host;
+    if (host == NULL || host->removed) {
+        INFO("no longer updating instance %p because host is no longer valid.", instance);
+        return;
+    }
+    INFO("re-registering updating instance %p.", instance);
+    register_instance(instance);
+}
+
+static void
+srp_schedule_instance_retry(adv_instance_t *instance)
+{
+    GENERATE_WAKEUP(&instance->retry_wakeup) {
+        if (instance->wakeup_interval == 0) {
+            instance->wakeup_interval = 5 * 1000;
+        } else {
+            instance->wakeup_interval *= 2;
+        }
+        unsigned interval = instance->wakeup_interval * 1.5 - (srp_random32() % instance->wakeup_interval);
+        RETAIN_HERE(instance, adv_instance);
+        ioloop_add_wake_event(instance->retry_wakeup, instance, srp_instance_retry_callback, adv_instance_context_release, interval);
+        INFO("will attempt to reregister instance %p in %.3lf seconds", instance, ((double)interval) / 1000.0);
+    }
+}
+
+static void
+srp_host_record_retry_callback(void *context)
+{
+    adv_host_t *host = context;
+    if (host != NULL) {
+        host->re_register_pending = false;
+    }
+    if (host == NULL || host->removed) {
+        INFO("no longer updating host %p because host is no longer valid.", host);
+        return;
+    }
+
+    if (host->addresses != NULL) {
+        for (int i = 0; i < host->addresses->num; i++) {
+            adv_record_t *record = host->addresses->vec[i];
+            if (record != NULL) {
+                INFO("re-registering host record %p.", record);
+                register_host_record(host, record, false);
+            }
+        }
+    }
+    if (host->key_record != NULL) {
+        INFO("re-registering host record %p.", host->key_record);
+        register_host_record(host, host->key_record, false);
+    }
+}
+
+static void
+srp_schedule_host_record_retry(adv_record_t *record)
+{
+    // If the host isn't valid or we're already re-registering, don't schedule a retry.
+    if (record->host == NULL || record->host->removed) {
+        INFO("will not attempt to reregister record %p", record);
+        return;
+    }
+    if (record->host->re_register_pending) {
+        INFO("already scheduled attempt to reregister record %p", record);
+        return;
+    }
+
+    adv_host_t *host = record->host;
+    GENERATE_WAKEUP(&host->re_register_wakeup) {
+        if (host->wakeup_interval == 0) {
+            host->wakeup_interval = 5 * 1000;
+        } else {
+            host->wakeup_interval *= 2;
+        }
+        unsigned interval = host->wakeup_interval * 1.5 - (srp_random32() % host->wakeup_interval);
+        RETAIN_HERE(host, adv_host);
+        ioloop_add_wake_event(host->re_register_wakeup, host, srp_host_record_retry_callback, srp_adv_host_context_release, interval);
+        INFO("will attempt to reregister record %p in %.3lf seconds", record, ((double)interval) / 1000.0);
+    }
+}
+
+
 // When the host registration has completed, we get this callback.   Completion either means that we succeeded in
 // registering the record, or that something went wrong and the registration has failed.
 static void
@@ -1627,32 +2015,43 @@
         instance->update = NULL;
     }
 
-    if (error_code == kDNSServiceErr_NoError) {
+    if (error_code == kDNSServiceErr_NoError || error_code == kDNSServiceErr_NameConflict) {
         INFO("registration for service " PRI_S_SRP "." PRI_S_SRP "." PRI_S_SRP " -> "
-             PRI_S_SRP " has completed.", instance->instance_name, instance->service_type, domain,
-             host->registered_name);
+             PRI_S_SRP " has completed" PUB_S_SRP ".", instance->instance_name, instance->service_type, domain,
+             host->registered_name, error_code == kDNSServiceErr_NoError ? ":" : " with a conflict");
         INFO("registration is under " PRI_S_SRP "." PRI_S_SRP PRI_S_SRP, name, regtype,
              domain);
 
+        if (error_code != kDNSServiceErr_NoError) {
+            if (instance->txn == NULL) {
+                FAULT("instance->txn is NULL for instance %p!", instance);
+            } else {
+                ioloop_dnssd_txn_cancel_srp(host->server_state, instance->txn);
+                ioloop_dnssd_txn_release(instance->txn);
+                instance->txn = NULL;
+            }
+            srp_schedule_instance_retry(instance);
+        }
+
         // In principle update->instance should always be non-NULL here because a no-error response should
         // only happen once or not at all. But just to be safe...
         if (update != NULL) {
             if (instance->update_pending) {
                 if (update->client != NULL) {
-                    instance->recent_message = (ptrdiff_t)update->client->message; // for comparison later in update_finished
+                    instance->recent_message = (ptrdiff_t)update->client->message; // for comparison later in srp_mdns_update_finished
                 }
                 update->num_instances_completed++;
                 if (update->num_records_completed == update->num_records_started &&
                     update->num_instances_completed == update->num_instances_started)
                 {
-                    update_finished(update);
+                    srp_mdns_update_finished(update);
                 }
                 RELEASE_HERE(update, adv_update);
                 instance->update_pending = false;
                 update = NULL;
             }
         } else {
-            ERROR("no error, but update is NULL for instance " PRI_S_SRP " (" PRI_S_SRP
+            INFO("re-update succeeded for instance " PRI_S_SRP " (" PRI_S_SRP
                   " " PRI_S_SRP " " PRI_S_SRP ")", instance->instance_name, name, regtype, domain);
         }
     } else {
@@ -1676,6 +2075,7 @@
                     instance->update = NULL;
                 }
                 RELEASE_HERE(update, adv_update);
+            } else {
             }
         }
 
@@ -1684,7 +2084,7 @@
         if (instance->txn == NULL) {
             FAULT("instance->txn is NULL for instance %p!", instance);
         } else {
-            ioloop_dnssd_txn_cancel(instance->txn);
+            ioloop_dnssd_txn_cancel_srp(host->server_state, instance->txn);
             ioloop_dnssd_txn_release(instance->txn);
             instance->txn = NULL;
         }
@@ -1737,6 +2137,47 @@
     strftime(buf, buf_len, "%F %T", &tm_now);
 }
 
+DNSServiceAttributeRef
+srp_message_tsr_attribute_generate(message_t *message, uint32_t key_id, char *time_buf, size_t time_buf_size)
+{
+    DNSServiceAttributeRef attribute = DNSServiceAttributeCreate();
+    if (attribute == NULL) {
+        ERROR("Failed to create new DNSServiceAttributeRef");
+    } else {
+        uint32_t offset = 0;
+
+        if (message != NULL && message->received_time != 0) {
+            offset = (uint32_t)(srp_time() - message->received_time);
+            srp_format_time_offset(time_buf, time_buf_size, offset);
+        } else {
+            static char msg[] = "now";
+            if (time_buf_size < sizeof(msg)) {
+                FAULT("bogus time buf size %zd", time_buf_size);
+                time_buf[0] = 0;
+            } else {
+                memcpy(time_buf, msg, sizeof(msg));
+            }
+        }
+        if (_DNSSD_API_AVAILABLE_FALL_2024) {
+            DNSServiceAttributeSetHostKeyHash(attribute, key_id);
+        }
+        DNSServiceAttributeSetTimestamp(attribute, offset);
+    }
+    return attribute;
+}
+
+DNSServiceAttributeRef
+srp_adv_instance_tsr_attribute_generate(adv_instance_t *instance, char *time_buf, size_t time_buf_size)
+{
+    message_t *message = NULL;
+    if (instance->update != NULL && instance->update->client != NULL && instance->update->client->message != NULL) {
+        message = instance->update->client->message;
+    } else if (instance->update == NULL && instance->message != NULL) {
+        message = instance->message;
+    }
+    return srp_message_tsr_attribute_generate(message, instance->host->key_id, time_buf, time_buf_size);
+}
+
 static bool
 register_instance(adv_instance_t *instance)
 {
@@ -1745,54 +2186,44 @@
     srp_server_t *server_state = instance->host->server_state;
 
     // If we don't yet have a shared connection, create one.
-    if (!setup_shared_registration_txn(server_state)) {
+    if (!srp_mdns_shared_registration_txn_setup(server_state)) {
         goto exit;
     }
     DNSServiceRef service_ref = server_state->shared_registration_txn->sdref;
 
-    INFO("DNSServiceRegister(%p, " PRI_S_SRP ", " PRI_S_SRP ", " PRI_S_SRP ", %d, %p)",
-         service_ref, instance->instance_name, instance->service_type, instance->host->registered_name, instance->port, instance);
+    INFO(PUB_S_SRP "DNSServiceRegister(%p, " PRI_S_SRP ", " PRI_S_SRP ", " PRI_S_SRP ", %d, %p)",
+         instance->skip_update ? "skipping " : "", service_ref, instance->instance_name, instance->service_type,
+         instance->host->registered_name, instance->port, instance);
 
-    if (0) {
-#ifdef STUB_ROUTER
-    } else if (server_state->stub_router_enabled) {
-        DNSServiceAttributeRef attr = DNSServiceAttributeCreate();
-        if (attr == NULL) {
-            ERROR("Failed to create new DNSServiceAttributeRef");
-            err = kDNSServiceErr_NoMemory;
-        } else {
-            uint32_t offset = 0;
-            char time_buf[28];
-
-            if (instance->update->client != NULL && instance->update->client->message != NULL &&
-                instance->update->client->message->received_time != 0)
-            {
-                offset = (uint32_t)(srp_time() - instance->update->client->message->received_time);
-                srp_format_time_offset(time_buf, sizeof(time_buf), offset);
-            } else {
-                static char msg[] = "now";
-                memcpy(time_buf, msg, sizeof(msg));
-            }
-            DNSServiceAttributeSetTimestamp(attr, offset);
-            err = DNSServiceRegisterWithAttribute(&service_ref, (kDNSServiceFlagsShareConnection | kDNSServiceFlagsNoAutoRename),
-                                                  server_state->advertise_interface,
-                                                  instance->instance_name, instance->service_type, local_suffix,
-                                                  instance->host->registered_name, htons(instance->port), instance->txt_length,
-                                                  instance->txt_data, attr, register_instance_completion, instance);
-            DNSServiceAttributeDeallocate(attr);
-            if (err == kDNSServiceErr_NoError) {
-                INFO("DNSServiceRegister service_ref %p, TSR for " PRI_S_SRP " set to " PUB_S_SRP,
-                     service_ref, instance->host == NULL ? "<null>" : instance->host->name, time_buf);
-            }
+    if (instance->skip_update) {
+        if (instance->update->client != NULL) {
+            instance->recent_message = (ptrdiff_t)instance->update->client->message; // for comparison later in srp_mdns_update_finished
         }
-#endif // STUB_ROUTER
+        exit_status = true;
+        goto exit;
+    }
+
+    char time_buf[TSR_TIMESTAMP_STRING_LEN];
+    DNSServiceAttributeRef tsr_attribute =
+        srp_adv_instance_tsr_attribute_generate(instance, time_buf, sizeof(time_buf));
+    if (tsr_attribute == NULL) {
+        err = kDNSServiceErr_NoMemory;
     } else {
-        err = DNSServiceRegister(&service_ref,
-                                 kDNSServiceFlagsShareConnection | kDNSServiceFlagsNoAutoRename,
-                                 server_state->advertise_interface,
-                                 instance->instance_name, instance->service_type, local_suffix,
-                                 instance->host->registered_name, htons(instance->port), instance->txt_length,
-                                 instance->txt_data, register_instance_completion, instance);
+        uint32_t flags = kDNSServiceFlagsShareConnection | kDNSServiceFlagsNoAutoRename;
+        if (_DNSSD_API_AVAILABLE_FALL_2024) {
+            flags |= kDNSServiceFlagsKnownUnique;
+        }
+        err = dns_service_register_wa(server_state, &service_ref, flags,
+                                      server_state->advertise_interface,
+                                      instance->instance_name, instance->service_type, local_suffix,
+                                      instance->host->registered_name, htons(instance->port), instance->txt_length,
+                                      instance->txt_data, tsr_attribute, register_instance_completion, instance);
+        DNSServiceAttributeDeallocate(tsr_attribute);
+        if (err == kDNSServiceErr_NoError) {
+            INFO("DNSServiceRegister, TSR for instance " PRI_S_SRP " host " PRI_S_SRP " set to " PUB_S_SRP
+                 "(instance %p sdref %p)", instance->instance_name,
+                 instance->host->name == NULL ? "<null>" : instance->host->name, time_buf, instance, service_ref);
+        }
     }
 
     // This would happen if we pass NULL for regtype, which we don't, or if we run out of memory, or if
@@ -1804,11 +2235,11 @@
             if (err == 1) {
                 FAULT("bogus error code 1");
             }
-            INFO("DNSServiceRegister failed: " PUB_S_SRP ,
-                 err == kDNSServiceErr_ServiceNotRunning ? "not running" : "defunct");
+            INFO("DNSServiceRegister failed: " PUB_S_SRP " (instance %p)",
+                 err == kDNSServiceErr_ServiceNotRunning ? "not running" : "defunct", instance);
             service_disconnected(server_state, (intptr_t)server_state->shared_registration_txn);
         } else {
-            INFO("DNSServiceRegister failed: %d", err);
+            INFO("DNSServiceRegister failed: %d (instance %p)", err, instance);
         }
         goto exit;
     }
@@ -1962,27 +2393,21 @@
         adv_update_cancel(record->update);
         RELEASE_HERE(record->update, adv_update);
         record->update = NULL;
-        remove_shared_record(host->server_state, record); // This will prevent further callbacks and release the reference held by the transaction.
+        srp_mdns_shared_record_remove(host->server_state, record); // This will prevent further callbacks and release the reference held by the transaction.
         RELEASE_HERE(record, adv_record); // The callback has a reference to the record.
         RELEASE_HERE(record, adv_record); // Release the reference to the record that we retained at the beginning
         return;
 
     }
     update = record->update;
-    if (update == NULL) {
-        // We shouldn't ever get a callback with update==NULL (which means that the update completed successfully) that's not an
-        // error.
-        if (error_code == kDNSServiceErr_NoError) {
-            FAULT("update is NULL, registration for host record completed with invalid state.");
-        }
-    } else {
+    if (update != NULL) {
         RETAIN_HERE(update, adv_update);
     }
 
-    if (error_code == kDNSServiceErr_NoError) {
+    if (error_code == kDNSServiceErr_NoError || error_code == kDNSServiceErr_NameConflict) {
         // If the update is pending, it means that we just finished registering this record for the first time,
         // so we can count it as complete and check to see if there is any work left to do; if not, we call
-        // update_finished to apply the update to the host object.
+        // srp_mdns_update_finished to apply the update to the host object.
         const char *note = " has completed.";
         if (record->update_pending) {
             record->update_pending = false;
@@ -1991,13 +2416,20 @@
                 if (update->num_records_completed == update->num_records_started &&
                     update->num_instances_completed == update->num_instances_started)
                 {
-                    update_finished(update);
+                    srp_mdns_update_finished(update);
                 }
             }
         } else {
             note = " got spurious success callback after completion.";
         }
 
+        if (error_code != kDNSServiceErr_NoError) {
+            // Shared record is no longer good.
+            srp_mdns_shared_record_remove(host->server_state, record);
+            note = " completed with conflict.";
+            srp_schedule_host_record_retry(record);
+        }
+
         if (record->rrtype == dns_rrtype_a) {
             IPv4_ADDR_GEN_SRP(record->rdata, addr_buf);
             INFO("registration for host " PRI_S_SRP " address " PRI_IPv4_ADDR_SRP PUB_S_SRP,
@@ -2044,10 +2476,11 @@
                 update_failed(update, (error_code == kDNSServiceErr_NameConflict
                                        ? dns_rcode_yxdomain
                                        : dns_rcode_servfail), true, true);
+            } else {
             }
         }
         // Regardless of what else happens, this transaction is dead, so get rid of our references to it.
-        remove_shared_record(host->server_state, record);
+        srp_mdns_shared_record_remove(host->server_state, record);
     }
     if (update != NULL) {
         RELEASE_HERE(update, adv_update);
@@ -2060,7 +2493,7 @@
 {
     char service_type[DNS_MAX_LABEL_SIZE_ESCAPED * 2 + 2]; // sizeof '.' + sizeof '\0'.
     char instance_name[DNS_MAX_NAME_SIZE_ESCAPED + 1];
-    char *txt_data;
+    uint8_t *txt_data;
 
     // Allocate the raw registration
     adv_instance_t *instance = calloc(1, sizeof *instance);
@@ -2113,6 +2546,9 @@
         instance->txt_length = 0;
     }
 
+    // If the service_instance_t is marked to skip updating, mark the adv_instance_t as well.
+    instance->skip_update = raw->skip_update;
+
     return instance;
 }
 
@@ -2170,18 +2606,30 @@
     return true;
 }
 
+DNSServiceAttributeRef
+srp_adv_host_tsr_attribute_generate(adv_host_t *host, char *time_buf, size_t time_buf_size)
+{
+    message_t *message = NULL;
+    if (host->update != NULL && host->update->client != NULL && host->update->client->message != NULL) {
+        message = host->update->client->message;
+    } else if (host->update == NULL && host->message != NULL) {
+        message = host->message;
+    }
+    return srp_message_tsr_attribute_generate(message, host->key_id, time_buf, time_buf_size);
+}
+
 static bool
-register_host_record(adv_host_t *host, adv_record_t *record)
+register_host_record(adv_host_t *host, adv_record_t *record, bool skipping)
 {
     int err;
 
     // If this record is already registered, get rid of the old transaction.
-    if (record->rref != NULL) {
-        remove_shared_record(host->server_state, record);
+    if (record->rref != NULL && !skipping) {
+        srp_mdns_shared_record_remove(host->server_state, record);
     }
 
     // If we don't yet have a shared connection, create one.
-    if (!setup_shared_registration_txn(host->server_state)) {
+    if (!srp_mdns_shared_registration_txn_setup(host->server_state)) {
         return false;
     }
 
@@ -2189,65 +2637,51 @@
 
     if (record->rrtype == dns_rrtype_a) {
         IPv4_ADDR_GEN_SRP(record->rdata, rdata_buf);
-        INFO("DNSServiceRegisterRecord(%p %p %d %d %s %d %d %d " PRI_IPv4_ADDR_SRP " %d %p %p)",
-             service_ref, &record->rref, kDNSServiceFlagsShared, host->server_state->advertise_interface,
-             host->registered_name, record->rrtype, dns_qclass_in, record->rdlen,
-             IPv4_ADDR_PARAM_SRP(record->rdata, rdata_buf),
+        INFO(PUB_S_SRP "DNSServiceRegisterRecord(%p %p %d %d %s %d %d %d " PRI_IPv4_ADDR_SRP " %d %p %p)",
+             skipping ? "skipping " : "", service_ref, &record->rref, kDNSServiceFlagsShared,
+             host->server_state->advertise_interface, host->registered_name, record->rrtype, dns_qclass_in,
+             record->rdlen, IPv4_ADDR_PARAM_SRP(record->rdata, rdata_buf),
              ADDRESS_RECORD_TTL, register_host_record_completion, record);
     } else if (record->rrtype == dns_rrtype_aaaa) {
         SEGMENTED_IPv6_ADDR_GEN_SRP(record->rdata, rdata_buf);
-        INFO("DNSServiceRegisterRecord(%p %p %d %d %s %d %d %d " PRI_SEGMENTED_IPv6_ADDR_SRP " %d %p %p)",
-             service_ref, &record->rref, kDNSServiceFlagsShared, host->server_state->advertise_interface,
-             host->registered_name, record->rrtype, dns_qclass_in, record->rdlen,
-             SEGMENTED_IPv6_ADDR_PARAM_SRP(record->rdata, rdata_buf),
+        INFO(PUB_S_SRP "DNSServiceRegisterRecord(%p %p %d %d %s %d %d %d " PRI_SEGMENTED_IPv6_ADDR_SRP " %d %p %p)",
+             skipping ? "skipping " : "", service_ref, &record->rref, kDNSServiceFlagsShared,
+             host->server_state->advertise_interface, host->registered_name, record->rrtype, dns_qclass_in,
+             record->rdlen, SEGMENTED_IPv6_ADDR_PARAM_SRP(record->rdata, rdata_buf),
              ADDRESS_RECORD_TTL, register_host_record_completion, record);
     } else {
-        INFO("DNSServiceRegisterRecord(%p %p %d %d %s %d %d %d %p %d %p %p)",
-             service_ref, &record->rref,
+        INFO(PUB_S_SRP "DNSServiceRegisterRecord(%p %p %d %d %s %d %d %d %p %d %p %p)",
+             skipping ? "skipping " : "", service_ref, &record->rref,
              kDNSServiceFlagsShared,
              host->server_state->advertise_interface, host->registered_name,
              record->rrtype, dns_qclass_in, record->rdlen, record->rdata, ADDRESS_RECORD_TTL,
              register_host_record_completion, record);
     }
-
-    if (0) {
-#if STUB_ROUTER
-    } else if (host->server_state->stub_router_enabled) {
-        DNSServiceAttributeRef attr = DNSServiceAttributeCreate();
-        if (attr == NULL) {
-            ERROR("Failed to create new DNSServiceAttributeRef");
-            return false;
-        } else {
-            uint32_t offset = 0;
-            char time_buf[28];
-            if (host->update != NULL && host->update->client != NULL && host->update->client->message != NULL &&
-                host->update->client->message->received_time != 0)
-            {
-                offset = (uint32_t)(srp_time() - host->update->client->message->received_time);
-                srp_format_time_offset(time_buf, sizeof(time_buf), offset);
-            } else {
-                static char msg[] = "now";
-                memcpy(time_buf, msg, sizeof(msg));
-            }
-            DNSServiceAttributeSetTimestamp(attr, offset);
-            err = DNSServiceRegisterRecordWithAttribute(service_ref, &record->rref,
-                                                        kDNSServiceFlagsUnique,
-                                                        host->server_state->advertise_interface, host->registered_name,
-                                                        record->rrtype, dns_qclass_in, record->rdlen, record->rdata,
-                                                        ADDRESS_RECORD_TTL, attr, register_host_record_completion,
-                                                        record);
-            DNSServiceAttributeDeallocate(attr);
-            if (err == kDNSServiceErr_NoError) {
-                INFO("DNSServiceRegisterRecord rref = %p, TSR for " PRI_S_SRP " set to " PUB_S_SRP, record->rref, host->name, time_buf);
-            }
-        }
-#endif
-    } else {
-        err = DNSServiceRegisterRecord(service_ref, &record->rref, kDNSServiceFlagsUnique,
-                                       host->server_state->advertise_interface, host->registered_name,
-                                       record->rrtype, dns_qclass_in, record->rdlen, record->rdata, ADDRESS_RECORD_TTL,
-                                       register_host_record_completion, record);
+    // If we're skipping, we don't actually have to do any work.
+    if (skipping) {
+        return true;
     }
+
+    char time_buf[TSR_TIMESTAMP_STRING_LEN];
+    DNSServiceAttributeRef tsr_attribute =
+        srp_adv_host_tsr_attribute_generate(host, time_buf, sizeof(time_buf));
+    if (tsr_attribute == NULL) {
+        ERROR("Failed to create new DNSServiceAttributeRef");
+        return false;
+    } else {
+        err = dns_service_register_record_wa(host->server_state, service_ref, &record->rref,
+                                             kDNSServiceFlagsKnownUnique,
+                                             host->server_state->advertise_interface, host->registered_name,
+                                             record->rrtype, dns_qclass_in, record->rdlen, record->rdata,
+                                             ADDRESS_RECORD_TTL, tsr_attribute, register_host_record_completion,
+                                             record);
+        DNSServiceAttributeDeallocate(tsr_attribute);
+        if (err == kDNSServiceErr_NoError) {
+            INFO("DNSServiceRegisterRecord for " PRI_S_SRP ", TSR set to " PUB_S_SRP " (record %p rref %p)",
+                 host->name, time_buf, record, record->rref);
+        }
+    }
+
     if (err != kDNSServiceErr_NoError) {
         if (err == kDNSServiceErr_ServiceNotRunning || err == kDNSServiceErr_DefunctConnection ||
             err == kDNSServiceErr_BadParam || err == kDNSServiceErr_BadReference || err == 1)
@@ -2255,22 +2689,24 @@
             if (err == 1) { // This is for an old bug that probably doesn't happen anymore.
                 FAULT("bogus error code 1");
             }
-            INFO("DNSServiceRegisterRecord failed on host " PUB_S_SRP ": " PUB_S_SRP, host->name,
-                 err == kDNSServiceErr_ServiceNotRunning ? "not running" : "defunct");
+            INFO("DNSServiceRegisterRecord failed on host " PUB_S_SRP ": " PUB_S_SRP " (record %p)", host->name,
+                 err == kDNSServiceErr_ServiceNotRunning ? "not running" : "defunct", record);
             service_disconnected(host->server_state, (intptr_t)host->server_state->shared_registration_txn);
         } else {
-            INFO("DNSServiceRegisterRecord failed: %d", err);
+            INFO("DNSServiceRegisterRecord failed: %d (record %p)", err, record);
         }
         return false;
     }
     record->shared_txn = (intptr_t)host->server_state->shared_registration_txn;
     RETAIN_HERE(record, adv_record); // for the callback
-    record->update_pending = true;
+    if (host->update != NULL) {
+        record->update_pending = true;
+    }
     return true;
 }
 
 static bool
-update_instance_tsr(adv_instance_t *instance, bool rdata_changed)
+update_instance_tsr(adv_instance_t *instance, adv_instance_t *new_instance)
 {
     int err = kDNSServiceErr_NoError;
     bool success = false;
@@ -2284,29 +2720,37 @@
         goto out;
     }
     // Currently if we want to update the rdata, we need to do that separately from the TSR.
-    if (rdata_changed) {
-        err = DNSServiceUpdateRecord(instance->txn->sdref,
-                                     NULL, 0, instance->txt_length, instance->txt_data, 0);
+    if (new_instance != NULL) {
+        if (instance->skip_update) {
+            err = kDNSServiceErr_NoError;
+        } else {
+            err = dns_service_update_record(instance->host->server_state, instance->txn->sdref,
+                                            NULL, 0, new_instance->txt_length, new_instance->txt_data, 0);
+        }
         if (err != kDNSServiceErr_NoError) {
-            INFO("DNSServiceUpdateRecord for instance " PRI_S_SRP " TXT record failed: %d", instance->instance_name, err);
+            INFO("DNSServiceUpdateRecord for instance " PRI_S_SRP " TXT record failed: %d (instance %p)",
+                 instance->instance_name, err, instance);
             goto out;
         } else {
-            INFO("updated TXT record for " PRI_S_SRP ".", instance->instance_name);
+            INFO("updated TXT record for " PRI_S_SRP " . " PRI_S_SRP " (instance %p sdref %p).",
+                 instance->instance_name, instance->service_type, instance, instance->txn->sdref);
             success = true;
         }
     }
 
-#if STUB_ROUTER
     DNSServiceAttributeRef attr;
 
-    if (instance->host->server_state->stub_router_enabled) {
+    if (instance->skip_update) {
+        INFO("skipping DNSServiceUpdateRecord for instance " PRI_S_SRP " TSR (instance %p)",
+             instance->instance_name, instance);
+    } else {
         success = false;
         attr = DNSServiceAttributeCreate();
         if (attr == NULL) {
             ERROR("failed to create new DNSServiceAttributeRef");
         } else {
             uint32_t offset = 0;
-            char time_buf[28];
+            char time_buf[TSR_TIMESTAMP_STRING_LEN];
             if (instance->update != NULL && instance->update->client != NULL && instance->update->client->message != NULL &&
                 instance->update->client->message->received_time != 0)
             {
@@ -2316,34 +2760,44 @@
                 static char msg[] = "now";
                 memcpy(time_buf, msg, sizeof(msg));
             }
+            if (_DNSSD_API_AVAILABLE_FALL_2024) {
+                DNSServiceAttributeSetHostKeyHash(attr, instance->host->key_id);
+            }
             DNSServiceAttributeSetTimestamp(attr, offset);
-            err = DNSServiceUpdateRecordWithAttribute(instance->txn->sdref, NULL, 0, 0, NULL, 0, attr);
+            err = dns_service_update_record_wa(instance->host->server_state,
+                                               instance->txn->sdref, NULL, 0, 0, NULL, 0, attr);
             DNSServiceAttributeDeallocate(attr);
             if (err == kDNSServiceErr_NoError) {
-                INFO("DNSServiceRegisterUpdateRecord TSR for " PRI_S_SRP " set to " PUB_S_SRP,
-                     instance->host == NULL ? "<null>" : instance->host->name, time_buf);
+                INFO("DNSServiceUpdateRecord for " PRI_S_SRP ", TSR set to " PUB_S_SRP " (instance %p sdref %p)",
+                     instance->host == NULL ? "<null>" : instance->host->name, time_buf, instance, instance->txn->sdref);
                 success = true;
             } else {
-                INFO("DNSServiceUpdateRecord for instance " PRI_S_SRP " TSR failed: %d", instance->instance_name, err);
+                INFO("DNSServiceUpdateRecord for instance " PRI_S_SRP ", TSR failed: %d (instance %p sdref %p)",
+                     instance->instance_name, err, instance, instance->txn->sdref);
             }
         }
     }
-#endif // STUB_ROUTER
 
 out:
     if (success == false) {
         if (instance->txn != NULL) {
-            // We should never get a bad reference error; if we do, it's likely the result of a previous
-            // mDNSResponder crash. In this case, the sdref is no longer valid (that's what the error is
-            // saying). So we should NULL it out.
+            // We should never get a bad reference error.
             if (err == kDNSServiceErr_BadReference || err == kDNSServiceErr_BadParam) {
-                instance->txn->sdref = NULL;
+                FAULT("we got a bad reference error: why?");
             }
             // For all errors, we should cancel and release the transaction.
-            ioloop_dnssd_txn_cancel(instance->txn);
+            ioloop_dnssd_txn_cancel_srp(instance->host->server_state, instance->txn);
             ioloop_dnssd_txn_release(instance->txn);
             instance->txn = NULL;
         }
+    } else if (new_instance != NULL) {
+        // If we have new_instance, the caller is going to get rid of it, so we need to
+        // steal the (possibly changed) data from it and put it on instance.
+        free(instance->txt_data);
+        instance->txt_data = new_instance->txt_data;
+        instance->txt_length = new_instance->txt_length;
+        new_instance->txt_data = NULL;
+        new_instance->txt_length = 0;
     }
     return success;
 }
@@ -2351,7 +2805,6 @@
 static void
 update_host_tsr(adv_record_t *record, adv_update_t *update)
 {
-#if STUB_ROUTER
     DNSServiceAttributeRef attr;
     int err;
     dnssd_txn_t *shared_txn;
@@ -2371,34 +2824,34 @@
         return;
     }
 
-    if (record->host->server_state->stub_router_enabled) {
-        attr = DNSServiceAttributeCreate();
-        if (attr == NULL) {
-            ERROR("failed to create new DNSServiceAttributeRef");
+    attr = DNSServiceAttributeCreate();
+    if (attr == NULL) {
+        ERROR("failed to create new DNSServiceAttributeRef");
+    } else {
+        uint32_t offset = 0;
+        char time_buf[TSR_TIMESTAMP_STRING_LEN];
+        if (update->client != NULL && update->client->message != NULL && update->client->message->received_time != 0) {
+            offset = (uint32_t)(srp_time() - update->client->message->received_time);
+            srp_format_time_offset(time_buf, sizeof(time_buf), offset);
         } else {
-            uint32_t offset = 0;
-            char time_buf[28];
-            if (update->client != NULL && update->client->message != NULL && update->client->message->received_time != 0) {
-                offset = (uint32_t)(srp_time() - update->client->message->received_time);
-                srp_format_time_offset(time_buf, sizeof(time_buf), offset);
-            } else {
-                static char msg[] = "now";
-                memcpy(time_buf, msg, sizeof(msg));
-            }
-            DNSServiceAttributeSetTimestamp(attr, offset);
-            err = DNSServiceUpdateRecordWithAttribute(shared_txn->sdref, record->rref, 0, 0, NULL, 0, attr);
-            DNSServiceAttributeDeallocate(attr);
-            if (err == kDNSServiceErr_NoError) {
-                INFO("DNSServiceUpdateRecord TSR for " PRI_S_SRP " set to " PUB_S_SRP,
-                     record->host == NULL ? "<null>" : record->host->name, time_buf);
-            } else {
-                INFO("DNSServiceUpdateRecordWithAttribute for host tsr failed: %d", err);
-            }
+            static char msg[] = "now";
+            memcpy(time_buf, msg, sizeof(msg));
+        }
+        if (_DNSSD_API_AVAILABLE_FALL_2024) {
+            DNSServiceAttributeSetHostKeyHash(attr, record->host->key_id);
+        }
+        DNSServiceAttributeSetTimestamp(attr, offset);
+        err = dns_service_update_record_wa(record->host->server_state,
+                                           shared_txn->sdref, record->rref, 0, 0, NULL, 0, attr);
+        DNSServiceAttributeDeallocate(attr);
+        if (err == kDNSServiceErr_NoError) {
+            INFO("DNSServiceUpdateRecord TSR for " PRI_S_SRP " set to " PUB_S_SRP " (record %p rref %p)",
+                 record->host == NULL ? "<null>" : record->host->name, time_buf, record, record->rref);
+        } else {
+            INFO("DNSServiceUpdateRecordWithAttribute for host tsr failed: %d (record %p rref %p)",
+                 err, record, record->rref);
         }
     }
-#else
-    (void)record; (void)update;
-#endif
 }
 
 // When we need to register a host with mDNSResponder, start_host_update is called.   This can be either because
@@ -2419,16 +2872,19 @@
         return;
     }
 
+    bool skip_host_updates = (update->client != NULL && update->client->skip_host_updates);
+
+
     update->num_records_started = 0;
 
     // Add all of the addresses that have been registered.
     if (update->add_addresses != NULL) {
         for (i = 0; i < update->add_addresses->num; i++) {
             if (update->add_addresses->vec[i] != NULL) {
-                if (!register_host_record(host, update->add_addresses->vec[i])) {
+                if (!register_host_record(host, update->add_addresses->vec[i], skip_host_updates)) {
                     update_failed(update, dns_rcode_servfail, true, true);
                     return;
-                } else {
+                } else if (!skip_host_updates) {
                     update->num_records_started++;
                 }
             }
@@ -2441,13 +2897,17 @@
     if (host->addresses != NULL) {
         for (i = 0; i < host->addresses->num; i++) {
             adv_record_t *record = host->addresses->vec[i];
-            if (update->remove_addresses->vec[i] == NULL && record != NULL && record->rref == NULL) {
+            adv_record_t *remove_address = NULL;
+            if (update->remove_addresses != NULL) {
+                remove_address = update->remove_addresses->vec[i];
+            }
+            if (remove_address == NULL && record != NULL && record->rref == NULL) {
                 host->addresses->vec[i]->update = update;
                 RETAIN_HERE(host->addresses->vec[i]->update, adv_update);
-                if (!register_host_record(host, record)) {
+                if (!register_host_record(host, record, skip_host_updates)) {
                     update_failed(update, dns_rcode_servfail, true, true);
                     return;
-                } else {
+                } else if (!skip_host_updates) {
                     update->num_records_started++;
                 }
             }
@@ -2455,11 +2915,12 @@
     }
 
     if (update->key != NULL) {
-        if (!register_host_record(host, update->key)) {
+        if (!register_host_record(host, update->key, skip_host_updates)) {
             update_failed(update, dns_rcode_servfail, true, true);
             return;
+        } else if (!skip_host_updates) {
+            update->num_records_started++;
         }
-        update->num_records_started++;
     }
 
     // If the shared transaction has changed since the key record was added, add it again.
@@ -2474,11 +2935,12 @@
         update->key->rref = NULL;
         update->key->update = update;
         RETAIN_HERE(update, adv_update);
-        if (!register_host_record(host, update->key)) {
+        if (!register_host_record(host, update->key, skip_host_updates)) {
             update_failed(update, dns_rcode_servfail, true, true);
             return;
+        } else if (!skip_host_updates) {
+            update->num_records_started++;
         }
-        update->num_records_started++;
     }
 
     if (update->num_records_started == 0) {
@@ -2486,12 +2948,13 @@
         if (record == NULL) {
         } else {
             if (record->rref == NULL) {
-                if (!register_host_record(host, record)) {
+                if (!register_host_record(host, record, skip_host_updates)) {
                     update_failed(update, dns_rcode_servfail, true, true);
                     return;
+                } else if (!skip_host_updates) {
+                    update->num_records_started++;
                 }
-                update->num_records_started++;
-            } else {
+            } else if (!skip_host_updates) {
                 update_host_tsr(record, update);
             }
         }
@@ -2517,23 +2980,21 @@
                 if (update->renew_instances->vec[i] != NULL) {
                     adv_instance_t *instance = update->renew_instances->vec[i];
                     bool must_update = true;
+                    bool renew_failed = instance->txn != NULL;
                     if (instance->txn != NULL) {
                         bool must_remove = false;
                         // Make sure the instance is still registered and is registered on the current shared connection.
                         if (instance->txn->sdref != NULL) {
                             if (((intptr_t)host->server_state->shared_registration_txn == instance->shared_txn)) {
-#if STUB_ROUTER
-                                if (!host->server_state->stub_router_enabled || update_instance_tsr(instance, false)) {
-#endif
+                                if (update_instance_tsr(instance, NULL)) {
                                     must_remove = false;
                                     must_update = false;
-#if STUB_ROUTER
+                                    instance->recent_message = (ptrdiff_t)update->client->message;
                                 } else {
                                     INFO("instance " PRI_S_SRP " (%p) tsr update failed, re-registering",
                                          instance->instance_name, instance);
                                     must_remove = true;
                                 }
-#endif
                             } else {
                                 // If the shared transaction has changed, then the registration no longer exists, and
                                 // the sdref is no longer valid.
@@ -2541,19 +3002,23 @@
                                      instance->instance_name, instance, instance->shared_txn);
                                 instance->txn->sdref = NULL;
                                 must_remove = true;
+                                must_update = true;
+                                renew_failed = false;
                             }
                         }
                         if (must_remove) {
                             // If not, dispose of the transaction and re-register.
                             if (instance->txn != NULL) {
-                                ioloop_dnssd_txn_cancel(instance->txn);
+                                ioloop_dnssd_txn_cancel_srp(host->server_state, instance->txn);
                                 ioloop_dnssd_txn_release(instance->txn);
                                 instance->txn = NULL;
                             }
                         }
                     }
                     if (must_update) {
-                        INFO(PRI_S_SRP " (%p): failed to update TSR, re-registering", instance->instance_name, instance);
+                        if (renew_failed) {
+                            INFO(PRI_S_SRP " (%p): failed to update TSR, re-registering", instance->instance_name, instance);
+                        }
                         if (!register_instance(update->renew_instances->vec[i])) {
                             update_failed(update, dns_rcode_servfail, true, true);
                             return;
@@ -2586,25 +3051,33 @@
                 // if we used DNSServiceRegisterRecord rather than DNSServiceRegister, but currently we don't do that.
                 // Of course if the previous registration is no longer valid, re-register.
                 if (host_instance->txn != NULL && host_instance->txn->sdref != NULL && host->server_state != NULL &&
-                    ((intptr_t)host->server_state->shared_registration_txn == update_instance->shared_txn))
+                    ((intptr_t)host->server_state->shared_registration_txn == host_instance->shared_txn))
                 {
                     if (update_instance->port == host_instance->port &&
                         update_instance->txt_length != 0 &&
                         memcmp(update_instance->txt_data, host_instance->txt_data, update_instance->txt_length))
                     {
-                        update_instance_tsr(update_instance, true);
-                        must_register = false;
+                        // If we are able to update the TXT record using DNSServiceUpdateRecord, we don't actually need
+                        // this update instance.
+                        if (update_instance_tsr(host_instance, update_instance)) {
+                            host_instance->recent_message = (ptrdiff_t)update->client->message;
+                            RELEASE_HERE(update->update_instances->vec[i], adv_instance);
+                            update_instance = NULL;
+                            update->update_instances->vec[i] = NULL;
+                            must_register = false;
+                        }
                     }
                 }
                 if (must_register) {
                     if (host_instance->txn != NULL) {
-                        ioloop_dnssd_txn_cancel(host->instances->vec[i]->txn);
+                        ioloop_dnssd_txn_cancel_srp(host->server_state, host->instances->vec[i]->txn);
                         ioloop_dnssd_txn_release(host->instances->vec[i]->txn);
                         host->instances->vec[i]->txn = NULL;
                     }
 
                     if (!register_instance(update->update_instances->vec[i])) {
                         INFO("register instance failed.");
+                        update_failed(update, dns_rcode_servfail, true, true);
                         return;
                     }
                 }
@@ -2614,7 +3087,7 @@
 
     if (update->num_instances_started == 0 && update->num_records_started == 0) {
         INFO("no service or record updates, so we're finished.");
-        update_finished(update);
+        srp_mdns_update_finished(update);
         return;
     }
 
@@ -2673,6 +3146,10 @@
     }
     RETAIN_HERE(update, adv_update); // For the lifetime of this function
 
+    if (host->re_register_wakeup != NULL) {
+        ioloop_cancel_wake_event(host->re_register_wakeup);
+    }
+    host->re_register_pending = false;
     update->start_time = srp_time();
 
     // The maximum number of addresses we could be deleting is all the ones the host currently has.
@@ -2899,7 +3376,7 @@
 
         prepared_instance->anycast = false;
         if (client_update != NULL && client_update->connection != NULL) {
-            const struct sockaddr *server_addr = connection_get_local_address(client_update->connection);
+            const struct sockaddr *server_addr = connection_get_local_address(client_update->message);
             if (server_addr && server_addr->sa_family == AF_INET6) {
                 const struct in6_addr *const ipv6_address = &(((const struct sockaddr_in6 *)server_addr)->sin6_addr);
                 uint16_t server_port = ntohs(((const struct sockaddr_in6 *)server_addr)->sin6_port);
@@ -2971,15 +3448,23 @@
     update->host_lease = client_update->host_lease;
     update->key_lease = client_update->key_lease;
 
+    // Register any added addresses with threadradiod before we actually advertise them, to avoid a spurious
+    // address query.
+
     host->update = update;
     RETAIN_HERE(host->update, adv_update);
     RELEASE_HERE(update, adv_update);
     update = NULL;
 
+
     start_host_update(host);
     return;
 
 fail:
+    if (client_update != NULL) {
+        srp_parse_client_updates_free(client_update);
+        client_update = NULL;
+    }
     if (remove_addrs != NULL) {
         // Addresses in remove_addrs are owned by the host and don't need to be freed.
         RELEASE_HERE(remove_addrs, adv_record_vec);
@@ -3028,45 +3513,123 @@
 }
 
 bool
-srp_update_start(comm_t *connection, srp_server_t *server_state, srpl_connection_t *srpl_connection, dns_message_t *parsed_message, message_t *raw_message,
-                 dns_host_description_t *new_host, service_instance_t *instances, service_t *services,
-                 delete_t *removes, dns_name_t *update_zone, uint32_t lease_time, uint32_t key_lease_time,
-                 uint32_t serial_number, bool found_serial)
+srp_update_start(client_update_t *client_update)
 {
-    adv_host_t *host, **p_hosts = NULL;
+    dns_host_description_t *new_host = client_update->host;
+    char new_host_name[DNS_MAX_NAME_SIZE_ESCAPED + 1];
+    srp_server_t *server_state = client_update->server_state;
+    uint32_t key_id = 0;
+    dns_name_print(new_host->name, new_host_name, sizeof new_host_name);
+    adv_host_t *host = NULL;
+    srpl_connection_t *srpl_connection = client_update->srpl_connection;
+    message_t *raw_message = client_update->message;
+    comm_t *connection = client_update->connection;
+
+
+    // Compute a checksum on the key, ignoring up to three bytes at the end.
+    for (client_update_t *update = client_update; update != NULL; update = update->next) {
+        dns_host_description_t *update_host = update->host;
+
+        uint32_t update_key_id = 0;
+        for (unsigned i = 0; i < update_host->key->data.key.len; i += 4) {
+            update_key_id += ((update_host->key->data.key.key[i] << 24) | (update_host->key->data.key.key[i + 1] << 16) |
+                              (update_host->key->data.key.key[i + 2] << 8) | (update_host->key->data.key.key[i + 3]));
+        }
+        if (update == client_update) {
+            key_id = update_key_id;
+        } else if (key_id != update_key_id) {
+            ERROR("update contains multiple key ids %x and %x", key_id, update_key_id);
+            advertise_finished(NULL, new_host_name, server_state,
+                               srpl_connection, NULL, raw_message, dns_rcode_refused, NULL, false, true);
+            goto cleanup;
+        }
+    }
+
+    char seenbuf[200];
+    char *already_seen = seenbuf;
+    const char *plural = "";
+
+    // For replicated updates, check the transaction IDs to make sure we aren't applying an update we've already gotten.
+    if (srpl_connection != NULL) {
+        for (host = server_state->hosts; host != NULL; host = host->next) {
+            if (host->key_id == key_id && !strcmp(host->name, new_host_name)) {
+                break;
+            }
+        }
+
+        if (host != NULL) {
+            bool replay = true;
+            while (client_update != NULL && replay) {
+                replay = false;
+                if (host->message != NULL && host->message->wire.id == client_update->message->wire.id) {
+                    replay = true;
+                } else if (host->instances != NULL) {
+                    for (int i = 0; i < host->instances->num; i++) {
+                        adv_instance_t *instance = host->instances->vec[i];
+                        if (instance != NULL) {
+                            if (instance->message != NULL &&
+                                instance->message->wire.id == client_update->message->wire.id)
+                            {
+                                replay = true;
+                                break;
+                            }
+                        }
+                    }
+                }
+                if (replay) {
+                    client_update_t *skip_update = client_update;
+                    client_update = client_update->next;
+                    if (already_seen != seenbuf) {
+                        plural = "s";
+                    }
+                    if (already_seen + 6 < &seenbuf[sizeof(seenbuf)]) {
+                        snprintf(already_seen, 6, " %04x", skip_update->message->wire.id);
+                        already_seen += 5;
+                    }
+                    skip_update->next = NULL;
+                    srp_parse_client_updates_free(skip_update);
+                    if (client_update != NULL) {
+                        new_host = client_update->host;
+                    } else {
+                        new_host = NULL;
+                    }
+                }
+            }
+        }
+    }
+    if (already_seen != seenbuf) {
+        INFO("host update for " PRI_S_SRP ", key id %" PRIx32 " " PUB_S_SRP " (skipped xid" PUB_S_SRP PUB_S_SRP ")",
+             new_host_name, key_id, srpl_connection == NULL ? "" : srpl_connection->name, plural, seenbuf);
+    } else {
+        INFO("host update for " PRI_S_SRP ", key id %" PRIx32 " " PUB_S_SRP,
+             new_host_name, key_id, srpl_connection == NULL ? "" : srpl_connection->name);
+    }
+    if (client_update == NULL) {
+        advertise_finished(host, new_host_name, server_state, srpl_connection,
+                           NULL, raw_message, dns_rcode_noerror, NULL, false, true);
+        return true; // It's safe to just return here because we've freed all the client updates.
+    }
+
+    service_instance_t *instances = client_update->instances;
+    delete_t *removes = client_update->removes;
+    adv_host_t **p_hosts = NULL;
     char pres_name[DNS_MAX_NAME_SIZE_ESCAPED + 1];
     service_instance_t *new_instance;
     instance_outcome_t outcome = missed;
-    client_update_t *client_update;
     char instance_name[DNS_MAX_LABEL_SIZE_ESCAPED + 1];
     char service_type[DNS_MAX_LABEL_SIZE_ESCAPED * 2 + 2];
-    uint32_t key_id = 0;
-    char new_host_name[DNS_MAX_NAME_SIZE_ESCAPED + 1];
     host_addr_t *addr;
-    const bool remove = lease_time == 0;
-    const char *updatestr = lease_time == 0 ? "remove" : "update";
+    const bool remove = client_update->host_lease == 0;
+    const char *updatestr = client_update->host_lease == 0 ? "remove" : "update";
     delete_t *dp;
-    dns_name_print(new_host->name, new_host_name, sizeof new_host_name);
 
-    // Compute a checksum on the key, ignoring up to three bytes at the end.
-    for (unsigned i = 0; i < new_host->key->data.key.len; i += 4) {
-        key_id += ((new_host->key->data.key.key[i] << 24) | (new_host->key->data.key.key[i + 1] << 16) |
-                   (new_host->key->data.key.key[i + 2] << 8) | (new_host->key->data.key.key[i + 3]));
-    }
 
-    // Log the update info.
-    if (found_serial) {
-        INFO("host update for " PRI_S_SRP ", key id %" PRIx32 ", serial number %" PRIu32 " " PUB_S_SRP,
-             new_host_name, key_id, serial_number, srpl_connection == NULL ? "" : srpl_connection->name);
-    } else {
-        INFO("host update for " PRI_S_SRP ", key id %" PRIx32 " " PUB_S_SRP, new_host_name, key_id,
-             srpl_connection == NULL ? "" : srpl_connection->name);
-    }
     for (addr = new_host->addrs; addr != NULL; addr = addr->next) {
         if (addr->rr.type == dns_rrtype_a) {
             IPv4_ADDR_GEN_SRP(&addr->rr.data.a.s_addr, addr_buf);
             INFO("host " PUB_S_SRP " for " PRI_S_SRP ", address " PRI_IPv4_ADDR_SRP " " PUB_S_SRP, updatestr,
-                 new_host_name, IPv4_ADDR_PARAM_SRP(&addr->rr.data.a.s_addr, addr_buf), srpl_connection == NULL ? "" : srpl_connection->name);
+                 new_host_name, IPv4_ADDR_PARAM_SRP(&addr->rr.data.a.s_addr, addr_buf),
+                 srpl_connection == NULL ? "" : srpl_connection->name);
         } else {
             SEGMENTED_IPv6_ADDR_GEN_SRP(addr->rr.data.aaaa.s6_addr, addr_buf);
             INFO("host " PUB_S_SRP " for " PRI_S_SRP ", address " PRI_SEGMENTED_IPv6_ADDR_SRP " " PUB_S_SRP,
@@ -3131,7 +3694,7 @@
         ERROR("service instance name " PRI_S_SRP "/" PRI_S_SRP " already pointing to host "
               PRI_S_SRP ", not host " PRI_S_SRP, instance_name, service_type, host->name, new_host_name);
         advertise_finished(NULL, host->name,
-                           server_state, srpl_connection, connection, raw_message, dns_rcode_yxdomain, NULL, true);
+                           server_state, srpl_connection, connection, raw_message, dns_rcode_yxdomain, NULL, true, true);
         goto cleanup;
     }
 
@@ -3166,7 +3729,7 @@
                                 ERROR("remove for " PRI_S_SRP "." PRI_S_SRP " conflicts with instance on host " PRI_S_SRP,
                                       instance_name, service_type, rhp->name);
                                 advertise_finished(NULL, rhp->name, server_state, srpl_connection,
-                                                   connection, raw_message, dns_rcode_formerr, NULL, true);
+                                                   connection, raw_message, dns_rcode_formerr, NULL, true, true);
                                 goto cleanup;
                             }
                         }
@@ -3188,7 +3751,7 @@
                                         ERROR("remove for " PRI_S_SRP " conflicts with instance on update to host " PRI_S_SRP,
                                               instance->instance_name, rhp->name);
                                         advertise_finished(NULL, rhp->name, server_state, srpl_connection,
-                                                           connection, raw_message, dns_rcode_formerr, NULL, true);
+                                                           connection, raw_message, dns_rcode_formerr, NULL, true, true);
                                         goto cleanup;
                                     }
                                 }
@@ -3225,6 +3788,15 @@
                     if (remove) {
                         break;
                     }
+                    // if remove is more recent than this message (for example, we firt receive remove
+                    // from the actual client and then receive a stale update message from a replication
+                    // peer), we don't apply this message and end processing here.
+                    if (host->remove_received_time > client_update->message->received_time) {
+                        INFO("update for host " PRI_S_SRP " which has been deleted.", host->name);
+                        advertise_finished(NULL, host->name, server_state, srpl_connection,
+                                           connection, raw_message, dns_rcode_servfail, NULL, true, true);
+                        goto cleanup;
+                    }
                     *p_hosts = host->next;
                     host_invalidate(host);
                     RELEASE_HERE(host, adv_host);
@@ -3239,7 +3811,7 @@
                       " which doesn't match host key id %" PRIx32 ".",
                       host->name, key_id, host->key_id);
                 advertise_finished(NULL, host->name, server_state, srpl_connection,
-                                   connection, raw_message, dns_rcode_yxdomain, NULL, true);
+                                   connection, raw_message, dns_rcode_yxdomain, NULL, true, true);
                 goto cleanup;
             } else if (comparison < 0) {
                 break;
@@ -3251,7 +3823,7 @@
                   " conflicts with existing host " PRI_S_SRP " with key id %" PRIx32,
                   new_host_name, key_id, host->name, host->key_id);
             advertise_finished(NULL, host->name, server_state, srpl_connection,
-                               connection, raw_message, dns_rcode_yxdomain, NULL, true);
+                               connection, raw_message, dns_rcode_yxdomain, NULL, true, true);
             goto cleanup;
         }
     }
@@ -3263,15 +3835,14 @@
         if (remove) {
             ERROR("Remove for host " PRI_S_SRP " which doesn't exist.", new_host_name);
             advertise_finished(NULL, new_host_name, server_state, srpl_connection,
-                               connection, raw_message, dns_rcode_noerror, NULL, true);
+                               connection, raw_message, dns_rcode_noerror, NULL, true, true);
             goto cleanup;
         }
-
         host = calloc(1, sizeof *host);
         if (host == NULL) {
             ERROR("no memory for host data structure.");
             advertise_finished(NULL, new_host_name, server_state, srpl_connection,
-                               connection, raw_message, dns_rcode_servfail, NULL, true);
+                               connection, raw_message, dns_rcode_servfail, NULL, true, true);
             goto cleanup;
         }
         RETAIN_HERE(host, adv_host);
@@ -3280,7 +3851,7 @@
         if (host->instances == NULL) {
             ERROR("no memory for host instance vector.");
             advertise_finished(NULL, new_host_name, server_state, srpl_connection,
-                               connection, raw_message, dns_rcode_servfail, NULL, true);
+                               connection, raw_message, dns_rcode_servfail, NULL, true, true);
             RELEASE_HERE(host, adv_host);
             host = NULL;
             goto cleanup;
@@ -3289,7 +3860,7 @@
         if (host->addresses == NULL) {
             ERROR("no memory for host address vector.");
             advertise_finished(NULL, new_host_name, server_state, srpl_connection,
-                               connection, raw_message, dns_rcode_servfail, NULL, true);
+                               connection, raw_message, dns_rcode_servfail, NULL, true, true);
             RELEASE_HERE(host, adv_host);
             host = NULL;
             goto cleanup;
@@ -3302,7 +3873,7 @@
         if (host->lease_wakeup == NULL) {
             ERROR("no memory for wake event on host");
             advertise_finished(NULL, new_host_name, server_state, srpl_connection,
-                               connection, raw_message, dns_rcode_servfail, NULL, true);
+                               connection, raw_message, dns_rcode_servfail, NULL, true, true);
             RELEASE_HERE(host, adv_host);
             host = NULL;
             goto cleanup;
@@ -3314,7 +3885,7 @@
             host = NULL;
             ERROR("no memory for hostname.");
             advertise_finished(NULL, new_host_name, server_state, srpl_connection,
-                               connection, raw_message, dns_rcode_servfail, NULL, true);
+                               connection, raw_message, dns_rcode_servfail, NULL, true, true);
             goto cleanup;
         }
         host->key = *new_host->key;
@@ -3329,7 +3900,7 @@
             host = NULL;
             ERROR("no memory for host key.");
             advertise_finished(NULL, new_host_name, server_state, srpl_connection,
-                               connection, raw_message, dns_rcode_servfail, NULL, true);
+                               connection, raw_message, dns_rcode_servfail, NULL, true, true);
             goto cleanup;
         }
         memcpy(host->key_rdata, &new_host->key->data.key.flags, 2);
@@ -3370,12 +3941,11 @@
             INFO("dropping retransmission of in-progress update for host " PRI_S_SRP, host->name);
 #if SRP_FEATURE_REPLICATION
             srp_replication_advertise_finished(host, host->name, server_state, srpl_connection,
-                                               connection, dns_rcode_servfail);
+                                               connection, dns_rcode_servfail, true);
 #endif
         cleanup:
-            srp_update_free_parts(instances, NULL, services, removes, new_host);
-            dns_message_free(parsed_message);
-            return true;
+            srp_parse_client_updates_free(client_update);
+            return false;
 #ifdef SRP_DETECT_STALLS
         }
 #endif
@@ -3391,23 +3961,17 @@
             ioloop_message_release(host->message);
         }
         host->message = raw_message;
+        // remember the time when the message that removes the host was received
+        host->remove_received_time = host->message->received_time;
         ioloop_message_retain(host->message);
         advertise_finished(host, new_host_name, server_state, srpl_connection,
-                           connection, raw_message, dns_rcode_noerror, NULL, true);
+                           connection, raw_message, dns_rcode_noerror, NULL, true, true);
         goto cleanup;
     }
 
     // At this point we have an update and a host to which to apply it.  We may already be doing an earlier
     // update, or not.  Create a client update structure to hold the communication, so that when we are done,
     // we can respond.
-    client_update = calloc(1, sizeof *client_update);
-    if (client_update == NULL) {
-        ERROR("no memory for host data structure.");
-        advertise_finished(NULL, new_host_name, server_state, srpl_connection,
-                           connection, raw_message, dns_rcode_servfail, NULL, true);
-        goto cleanup;
-    }
-
     if (outcome == missed) {
         INFO("New host " PRI_S_SRP ", key id %" PRIx32 , host->name, host->key_id);
     } else {
@@ -3423,46 +3987,36 @@
         host->registered_name = host->name;
     }
 
-    if (connection != NULL) {
-        client_update->connection = connection;
-        ioloop_comm_retain(client_update->connection);
-    }
-    client_update->parsed_message = parsed_message;
-    client_update->message = raw_message;
-    ioloop_message_retain(client_update->message);
-    client_update->host = new_host;
-    client_update->instances = instances;
-    client_update->services = services;
-    client_update->removes = removes;
-    client_update->update_zone = update_zone;
-
     // We have to take the lease from the SRP update--the original registrar negotiated it, and if it's out
     // of our range, that's too bad (ish).
     if (raw_message->lease != 0) {
+        INFO("basing lease time on message: raw_message->lease = %d, raw_message->key_lease = %d",
+             raw_message->lease, raw_message->key_lease);
         client_update->host_lease = raw_message->lease;
         client_update->key_lease = raw_message->key_lease;
     } else {
-        if (lease_time < server_state->max_lease_time) {
-            if (lease_time < server_state->min_lease_time) {
+        if (client_update->host_lease < server_state->max_lease_time) {
+            if (client_update->host_lease < server_state->min_lease_time) {
+                INFO("basing lease time on server_state->min_lease_time: %d", server_state->min_lease_time);
                 client_update->host_lease = server_state->min_lease_time;
             } else {
-                client_update->host_lease = lease_time;
+                INFO("basing lease time on client_update->host_lease: %d", client_update->host_lease);
+                // client_update->host_lease = client_update->host_lease;
             }
         } else {
             client_update->host_lease = server_state->max_lease_time;
+                INFO("basing lease time on server_state->max_lease_time: %d", server_state->max_lease_time);
         }
-        if (key_lease_time < server_state->key_max_lease_time) {
-            if (key_lease_time < server_state->key_min_lease_time) {
+        if (client_update->key_lease < server_state->key_max_lease_time) {
+            if (client_update->key_lease < server_state->key_min_lease_time) {
                 client_update->key_lease = server_state->key_min_lease_time;
             } else {
-                client_update->key_lease = key_lease_time;
+                // client_update->key_lease = client_update->key_lease;
             }
         } else {
             client_update->key_lease = server_state->key_max_lease_time;
         }
     }
-    client_update->serial_number = serial_number;
-    client_update->serial_sent = found_serial;
 
 #if SRP_FEATURE_REPLICATION
     if (srpl_connection != NULL) {
@@ -3507,7 +4061,7 @@
 }
 
 srp_server_t *
-server_state_create(const char *name, int interface_index, int max_lease_time, int min_lease_time,
+server_state_create(const char *name, int max_lease_time, int min_lease_time,
                     int key_max_lease_time, int key_min_lease_time)
 {
     srp_server_t *server_state = calloc(1, sizeof(*server_state));
@@ -3520,7 +4074,10 @@
     server_state->min_lease_time = min_lease_time;
     server_state->key_max_lease_time = key_max_lease_time;
     server_state->key_min_lease_time = key_min_lease_time;
-    server_state->advertise_interface = interface_index;
+    server_state->priority = PRIORITY_DEFAULT;
+#if TARGET_OS_TV
+#endif
+    INFO("priority set to %d", server_state->priority);
     return server_state;
 }
 
@@ -3531,6 +4088,14 @@
 
     ioloop_dump_object_allocation_stats();
 
+    if (server_state->full_dump_count == 0) {
+        srp_dump_server_stats(server_state, true, true);
+        server_state->full_dump_count = 12;
+    } else {
+        srp_dump_server_stats(server_state, false, true);
+    }
+    --server_state->full_dump_count;
+
     // Do the next object memory allocation statistics dump in five minutes
     ioloop_add_wake_event(server_state->object_allocation_stats_dump_wakeup, server_state,
                           object_allocation_stats_dump_callback, NULL, 5 * 60 * 1000);
@@ -3542,8 +4107,18 @@
     int i;
     char *end;
     int log_stderr = false;
+#ifdef SRP_TEST_SERVER
+    char *test_to_run = NULL;
+    bool normal_srp_startup = false;
+#else
+    bool normal_srp_startup = true;
+#endif
+#if STUB_ROUTER
+    bool stub_router_enabled = false;
+#endif
+    bool thread_device_enabled = false;
 
-    srp_servers = server_state_create("srp-mdns-proxy", PLATFORM_ADVERTISE_INTERFACE,
+    srp_servers = server_state_create("srp-mdns-proxy",
                                       3600 * 27,     // max lease time one day plus 20%
                                       30,            // min lease time 30 seconds
                                       3600 * 24 * 7, // max key lease 7 days
@@ -3552,11 +4127,23 @@
         return 1;
     }
 
-    srp_servers->srp_replication_enabled = true;
+    if (normal_srp_startup) {
+        srp_servers->srp_replication_enabled = true;
 #  if SRP_FEATURE_NAT64
-    srp_servers->srp_nat64_enabled = true;
+        srp_servers->srp_nat64_enabled = true;
 #  endif
+    }
 
+
+    // Set the advertise interface
+    if (0) {
+#if STUB_ROUTER
+    } else if (stub_router_enabled) {
+        srp_servers->advertise_interface = kDNSServiceInterfaceIndexAny;
+#endif
+    } else {
+        srp_servers->advertise_interface = if_nametoindex("lo0");
+    }
     for (i = 1; i < argc; i++) {
         if (!strcmp(argv[i], "--max-lease-time")) {
             if (i + 1 == argc) {
@@ -3578,6 +4165,10 @@
             i++;
         } else if (!strcmp(argv[i], "--log-stderr")) {
             log_stderr = true;
+#ifdef LOG_FPRINTF_STDERR
+        } else if (!strcmp(argv[i], "--log-relative-timestamp")) {
+            srp_log_timestamp_relative = true;
+#endif
         } else if (!strcmp(argv[i], "--enable-replication")) {
             srp_servers->srp_replication_enabled = true;
         } else if (!strcmp(argv[i], "--disable-replication")) {
@@ -3590,6 +4181,14 @@
             if (end == argv[i + 1] || end[0] != 0) {
                 usage();
             }
+#ifdef SRP_TEST_SERVER
+        } else if (!strcmp(argv[i], "--test")) {
+            if (i + 1 == argc) {
+                usage();
+            }
+            test_to_run = argv[i + 1];
+            i++;
+#endif
 #if SRP_FEATURE_NAT64
         } else if (!strcmp(argv[i], "--enable-nat64")) {
             srp_servers->srp_nat64_enabled = true;
@@ -3604,95 +4203,99 @@
     // Setup log category for srp-mdns-prox and dnssd-proxy.
     OPENLOG("srp-mdns-proxy", log_stderr);
 
+#ifdef SRP_TEST_SERVER
+    INFO("srp-test-server starting, compiled on " PUB_S_SRP ", " PUB_S_SRP, __DATE__, __TIME__);
+#else
     INFO("--------------------------------"
          "srp-mdns-proxy starting, compiled on " PUB_S_SRP ", " PUB_S_SRP
          "--------------------------------", __DATE__, __TIME__);
+#endif
 
     if (!ioloop_init()) {
         return 1;
     }
 
+    if (normal_srp_startup) {
+#if THREAD_DEVICE
+        if (0) {
 #if STUB_ROUTER
-    if (stub_router_enabled) {
-        srp_servers->route_state = route_state_create(srp_servers, "srp-mdns-proxy");
-        if (srp_servers->route_state == NULL) {
+        } else if (stub_router_enabled) {
+            srp_servers->route_state = route_state_create(srp_servers, "srp-mdns-proxy");
+            if (srp_servers->route_state == NULL) {
+                return 1;
+            }
+#endif // STUB_ROUTER
+        }
+
+        if (!srp_mdns_shared_registration_txn_setup(srp_servers)) {
             return 1;
         }
-    }
-#endif // STUB_ROUTER
-
-    srp_servers->shared_registration_txn = dnssd_txn_create_shared();
-    if (srp_servers->shared_registration_txn == NULL) {
-        return 1;
-    }
-    dns_service_op_not_to_be_freed = srp_servers->shared_registration_txn->sdref;
+        dns_service_op_not_to_be_freed = srp_servers->shared_registration_txn->sdref;
+#endif // THREAD_DEVICE
 
 #if STUB_ROUTER
-    if (stub_router_enabled) {
-        // Set up the ULA early just in case we get an early registration, nat64 will use the ula
-        route_ula_setup(srp_servers->route_state);
-    }
+        if (stub_router_enabled) {
+            // Set up the ULA early just in case we get an early registration, nat64 will use the ula
+            route_ula_setup(srp_servers->route_state);
+        }
 #endif
 
+
 #if (SRP_FEATURE_COMBINED_SRP_DNSSD_PROXY)
-#  if STUB_ROUTER || !THREAD_DEVICE
-    if (0) {
-#    if STUB_ROUTER
-    } else if (!stub_router_enabled) {
-#    endif
-    } else {
         if (!init_dnssd_proxy(srp_servers)) {
-            ERROR("main: failed to setup dnssd-proxy");
-            return 1;
+            ERROR("failed to setup dnssd-proxy");
         }
-    }
-#  endif // STUB_ROUTER
 #endif // #if (SRP_FEATURE_COMBINED_SRP_DNSSD_PROXY)
 
 #if STUB_ROUTER
-    if (stub_router_enabled) {
-        if (!start_icmp_listener()) {
-            return 1;
+        if (stub_router_enabled) {
+            if (!start_icmp_listener()) {
+                ERROR("failed to start icmp listener");
+            }
         }
-    }
 #endif
 
 
-    infrastructure_network_startup(srp_servers->route_state);
+        infrastructure_network_startup(srp_servers->route_state);
 
-    if (adv_ctl_init(srp_servers) != kDNSServiceErr_NoError) {
-        ERROR("Can't start advertising proxy control server.");
-        return 1;
-    }
+        if (adv_ctl_init(srp_servers) != kDNSServiceErr_NoError) {
+            ERROR("Can't start advertising proxy control server.");
+        }
 
-    // We require one open file per service and one per instance.
-    struct rlimit limits;
-    if (getrlimit(RLIMIT_NOFILE, &limits) < 0) {
-        ERROR("getrlimit failed: " PUB_S_SRP, strerror(errno));
-        return 1;
-    }
+        // We require one open file per service and one per instance.
+        struct rlimit limits;
+        if (getrlimit(RLIMIT_NOFILE, &limits) < 0) {
+            ERROR("getrlimit failed: " PUB_S_SRP, strerror(errno));
+        }
 
-    if (limits.rlim_cur < 1024) {
-        if (limits.rlim_max < 1024) {
-            INFO("file descriptor hard limit is %llu", (unsigned long long)limits.rlim_max);
-            if (limits.rlim_cur != limits.rlim_max) {
-                limits.rlim_cur = limits.rlim_max;
+        if (limits.rlim_cur < 1024) {
+            if (limits.rlim_max < 1024) {
+                INFO("file descriptor hard limit is %llu", (unsigned long long)limits.rlim_max);
+                if (limits.rlim_cur != limits.rlim_max) {
+                    limits.rlim_cur = limits.rlim_max;
+                }
+            } else {
+                limits.rlim_cur = 1024;
             }
-        } else {
-            limits.rlim_cur = 1024;
+            if (setrlimit(RLIMIT_NOFILE, &limits) < 0) {
+                ERROR("setrlimit failed: " PUB_S_SRP, strerror(errno));
+            }
         }
-        if (setrlimit(RLIMIT_NOFILE, &limits) < 0) {
-            ERROR("setrlimit failed: " PUB_S_SRP, strerror(errno));
-        }
-    }
 
-    srp_proxy_init("local");
+        srp_proxy_init("local");
+#ifdef SRP_TEST_SERVER
+    } else  {
+        ioloop_run_async(srp_test_server_run_test, test_to_run);
+#endif
+    }
 
     srp_servers->object_allocation_stats_dump_wakeup = ioloop_wakeup_create();
     if (srp_servers->object_allocation_stats_dump_wakeup == NULL) {
         INFO("no memory for srp_servers->object_allocation_stats_dump_wakeup");
     } else {
-        // Do an object memory allocation statistics dump every five minutes
+        // Do an object memory allocation statistics dump every five minutes, and a full database dump every half hour
+        // starting after the first five minutes
+        srp_servers->full_dump_count = 1;
         object_allocation_stats_dump_callback(srp_servers);
     }
 
diff --git a/ServiceRegistration/srp-mdns-proxy.h b/ServiceRegistration/srp-mdns-proxy.h
index 4941f79..7381eeb 100644
--- a/ServiceRegistration/srp-mdns-proxy.h
+++ b/ServiceRegistration/srp-mdns-proxy.h
@@ -1,6 +1,6 @@
 /* srp-mdns-proxy.h
  *
- * Copyright (c) 2019-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2019-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -23,8 +23,6 @@
 #include <stddef.h> // For ptrdiff_t
 #include "ioloop-common.h" // for service_connection_t
 
-#define PLATFORM_ADVERTISE_INTERFACE kDNSServiceInterfaceIndexAny
-
 typedef struct adv_instance adv_instance_t;
 typedef struct adv_record_registration adv_record_t;
 typedef struct adv_host adv_host_t;
@@ -48,15 +46,50 @@
 typedef struct delete delete_t;
 typedef struct _cti_connection_t *cti_connection_t;
 typedef struct dnssd_proxy_advertisements dnssd_proxy_advertisements_t;
+#if SRP_FEATURE_DISCOVERY_PROXY_SERVER
+typedef struct dnssd_dp_proxy_advertisements dnssd_dp_proxy_advertisements_t;
+#endif
 typedef struct dnssd_client dnssd_client_t;
 typedef struct probe_state probe_state_t;
+typedef struct srpl_instance srpl_instance_t;
+typedef struct wanted_service wanted_service_t;
+
+#ifdef SRP_TEST_SERVER
+typedef struct dns_service_event dns_service_event_t;
+typedef struct test_state test_state_t;
+typedef struct srp_server_state srp_server_t;
+typedef struct srpl_connection srpl_connection_t;
+#endif
+
+#define TSR_TIMESTAMP_STRING_LEN 28
+
+enum {
+    PRIORITY_DEFAULT = 0,
+#if !defined(__OPEN__SOURCE__) && !defined(POSIX_BUILD)
+    PRIORITY_HOMEPOD_ODEON = 1,
+    PRIORITY_HOMEPOD_NO_ODEON = 25,
+    PRIORITY_APPLETV_WIFI = 50,
+    PRIORITY_APPLETV_ETHERNET = 75,
+#endif
+};
 
 // Server internal state
 struct srp_server_state {
+#if SRP_TEST_SERVER
+    srp_server_t *NULLABLE next;
+#endif
     char *NULLABLE name;
     adv_host_t *NULLABLE hosts;
     dnssd_txn_t *NULLABLE shared_registration_txn;
     srpl_domain_t *NULLABLE srpl_domains;
+    srpl_instance_t *NULLABLE unmatched_instances;
+#ifdef SRP_TEST_SERVER
+    dns_service_event_t *NULLABLE dns_service_events;
+    test_state_t *NULLABLE test_state;
+    comm_t *NULLABLE srpl_listener;
+    srpl_connection_t *NULLABLE connections; // list of connections that other srp servers create to connect to us
+    int server_id;
+#endif
 #if STUB_ROUTER
     route_state_t *NULLABLE route_state;
 #endif
@@ -65,8 +98,13 @@
     service_publisher_t *NULLABLE service_publisher;
     thread_tracker_t *NULLABLE thread_tracker;
     node_type_tracker_t *NULLABLE node_type_tracker;
+    char *NULLABLE wed_ext_address; // For Wake-on End Device, the extended MAC address, if we are in p2p mode
+    struct in6_addr wed_ml_eid;     // For Wake-on End Device, the ML-EID, if we are in p2p mode
 #endif
     dnssd_proxy_advertisements_t *NULLABLE dnssd_proxy_advertisements;
+#if SRP_FEATURE_DISCOVERY_PROXY_SERVER
+    dnssd_dp_proxy_advertisements_t *NULLABLE dnssd_dp_proxy_advertisements;
+#endif
     dnssd_client_t *NULLABLE dnssd_client;
     io_t *NULLABLE adv_ctl_listener;
     wakeup_t *NULLABLE srpl_browse_wakeup;
@@ -79,19 +117,20 @@
     uint32_t min_lease_time; // thirty seconds
     uint32_t key_max_lease_time;
     uint32_t key_min_lease_time; // thirty seconds
+    int full_dump_count;
 
+    uint32_t priority;
     uint16_t rloc16;
 
     bool have_rloc16;
     bool have_mesh_local_address;
     bool srp_replication_enabled;
     bool break_srpl_time;
+#if STUB_ROUTER
     bool stub_router_enabled;
+#endif
     bool srp_unicast_service_blocked;
     bool srp_anycast_service_blocked;
-#if SRP_FEATURE_NAT64
-    bool srp_nat64_enabled;
-#endif
 };
 
 struct adv_instance {
@@ -100,17 +139,20 @@
     intptr_t shared_txn;                 // The shared txn on which the txn for this instance's registration was created
     adv_host_t *NULLABLE host;           // Host to which this service instance belongs
     adv_update_t *NULLABLE update;       // Ongoing update that currently owns this instance, if any.
+    wakeup_t *NULLABLE retry_wakeup;     // In case we get a spurious name conflict.
     char *NONNULL instance_name;         // Single label instance name (future: service instance FQDN)
     char *NONNULL service_type;          // Two label service type (e.g., _ipps._tcp)
     int port;                            // Port on which service can be found.
-    char *NULLABLE txt_data;             // Contents of txt record
+    uint8_t *NULLABLE txt_data;          // Contents of txt record
     uint16_t txt_length;                 // length of txt record contents
     message_t *NULLABLE message;         // Message that produces the current value of this instance
     ptrdiff_t recent_message;            // Most recent message (never dereference--this is for comparison only).
     int64_t lease_expiry;                // Time when lease expires, relative to ioloop_timenow().
+    unsigned wakeup_interval;            // Exponential backoff interval
     bool removed;                        // True if this instance is being kept around for replication.
     bool update_pending;                 // True if we got a conflict while updating and are waiting to try again
     bool anycast;                        // True if service registration is through anycast service.
+    bool skip_update;                    // True if we shouldn't actually register this instance
 };
 
 // A record registration
@@ -129,6 +171,7 @@
 struct adv_host {
     int ref_count;
     srp_server_t *NULLABLE server_state;   // Server state to which this host belongs.
+    wakeup_t *NONNULL re_register_wakeup;  // Wakeup for retry when we run into a name conflict
     wakeup_t *NONNULL retry_wakeup;        // Wakeup for retry when we run into a temporary failure
     wakeup_t *NONNULL lease_wakeup;        // Wakeup at least expiry time
     adv_host_t *NULLABLE next;             // Hosts are maintained in a linked list.
@@ -145,17 +188,17 @@
     dns_rr_t key;                          // The key data represented as an RR; key->name is NULL.
     uint32_t key_id;                       // A possibly-unique id that is computed across the key for brevity in
                                            // debugging
+    unsigned wakeup_interval;              // Exponential backoff interval for re-registration
     int retry_interval;                    // Interval to wait before attempting to re-register after the daemon has
                                            // died.
     time_t update_time;                    // Time when the update completed.
+    time_t remove_received_time;           // Time when the remove is received
     uint64_t update_server_id;             // Server ID from server that sent the update
     uint64_t server_stable_id;             // Stable ID of server that got update from client (we're using server ULA).
     uint16_t key_rdlen;                    // Length of key
     uint8_t *NULLABLE key_rdata;           // Raw KEY rdata, suitable for DNSServiceRegisterRecord().
     uint32_t lease_interval;               // Interval for address lease
     uint32_t key_lease;                    // Interval for key lease
-    uint32_t serial_number;                // Client's serial number
-    bool have_serial_number;               // True if we have received or generate a serial number.
     int64_t lease_expiry;                  // Time when lease expires, relative to ioloop_timenow().
     bool removed;                          // True if this host has been removed (and is being kept for replication)
 
@@ -163,6 +206,8 @@
     // host registration has expired, and there happens to be another update in progress, then we want
     // to defer the host registration.
     bool update_pending;
+    bool re_register_pending;
+
 };
 
 struct adv_update {
@@ -234,21 +279,47 @@
 
 struct client_update {
     client_update_t *NULLABLE next;
-    comm_t *NONNULL connection;               // Connection on which in-process update was received.
-    dns_message_t *NONNULL parsed_message;    // Message that triggered the update.
-    message_t *NONNULL message;               // Message that triggered the update.
+    comm_t *NULLABLE connection;                 // Connection on which in-process update was received.
+    srpl_connection_t *NULLABLE srpl_connection; // SRP replication connection on which update was received.
+    dns_message_t *NULLABLE parsed_message;      // Message that triggered the update.
+    message_t *NULLABLE message;                 // Message that triggered the update.
 
-    dns_host_description_t *NONNULL host;     // Host data parsed from message
-    service_instance_t *NULLABLE instances;   // Service instances parsed from message
-    service_t *NONNULL services;              // Services parsed from message
-    delete_t *NULLABLE removes;               // Removes parsed from message
-    dns_name_t *NONNULL update_zone;          // Zone being updated
-    uint32_t host_lease, key_lease;           // Lease intervals for host entry and key entry.
-    uint32_t serial_number;                   // Serial number sent by client, if one was sent
-    bool serial_sent;                         // True if serial number was sent.
+    dns_host_description_t *NULLABLE host;       // Host data parsed from message
+    service_instance_t *NULLABLE instances;      // Service instances parsed from message
+    service_t *NULLABLE services;                // Services parsed from message
+    delete_t *NULLABLE removes;                  // Removes parsed from message
+    dns_name_t *NULLABLE update_zone;            // Zone being updated
+    srp_server_t *NULLABLE server_state;         // SRP server state associated with this update, for testing
+    uint32_t host_lease, key_lease;              // Lease intervals for host entry and key entry.
+    int index;                                   // Message number for multi-message SRP updates
+    uint8_t rcode;
+    bool skip;                                   // If true, this update is completely overshadowed by later updates and we
+                                                 // should skip it.
+    bool drop;                                   // If true, the signature on this message didn't validate and we mustn't
+                                                 // send a response
+    bool skip_host_updates;                      // If true, don't actually register any host records.
+};
+
+struct wanted_service {
+    wanted_service_t *NULLABLE next;
+    char *NULLABLE name;
+    char *NULLABLE service_type;
 };
 
 // Exported functions.
+bool srp_mdns_shared_registration_txn_setup(srp_server_t *NONNULL server_state);
+void srp_mdns_shared_record_remove(srp_server_t *NONNULL server_state, adv_record_t *NONNULL record);
+void srp_mdns_update_finished(adv_update_t *NONNULL update);
+#define adv_instance_retain(instance) adv_instance_retain_(instance, __FILE__, __LINE__)
+void adv_instance_retain_(adv_instance_t *NONNULL instance, const char *NONNULL file, int line);
+#define adv_instance_release(instance) adv_instance_release_(instance, __FILE__, __LINE__)
+void adv_instance_release_(adv_instance_t *NONNULL instance, const char *NONNULL file, int line);
+#define adv_record_retain(record) adv_record_retain_(record, __FILE__, __LINE__)
+void adv_record_retain_(adv_record_t *NONNULL record, const char *NONNULL file, int line);
+#define adv_record_release(record) adv_record_release_(record, __FILE__, __LINE__)
+void adv_record_release_(adv_record_t *NONNULL record, const char *NONNULL file, int line);
+void adv_instance_context_release(void *NONNULL context);
+
 #define srp_adv_host_release(host) srp_adv_host_release_(host, __FILE__, __LINE__)
 void srp_adv_host_release_(adv_host_t *NONNULL host, const char *NONNULL file, int line);
 #define srp_adv_host_retain(host) srp_adv_host_retain_(host, __FILE__, __LINE__)
@@ -259,8 +330,14 @@
 int srp_current_valid_host_count(srp_server_t *NONNULL server_state);
 int srp_hosts_to_array(srp_server_t *NONNULL server_state, adv_host_t *NONNULL *NULLABLE host_array, int max_hosts);
 bool srp_adv_host_valid(adv_host_t *NONNULL host);
-srp_server_t *NULLABLE server_state_create(const char *NONNULL name, int interface_index, int max_lease_time,
+srp_server_t *NULLABLE server_state_create(const char *NONNULL name, int max_lease_time,
                                            int min_lease_time, int key_max_lease_time, int key_min_lease_time);
+DNSServiceAttributeRef NULLABLE srp_adv_instance_tsr_attribute_generate(adv_instance_t *NONNULL instance,
+                                                                        char *NONNULL time_buf, size_t time_buf_size);
+DNSServiceAttributeRef NULLABLE srp_adv_host_tsr_attribute_generate(adv_host_t *NONNULL host,
+                                                                    char *NONNULL time_buf, size_t time_buf_size);
+DNSServiceAttributeRef NULLABLE srp_message_tsr_attribute_generate(message_t *NULLABLE message, uint32_t key_id,
+                                                                   char *NONNULL time_buf, size_t time_buf_size);
 #endif // __SRP_MDNS_PROXY_H__
 
 // Local Variables:
diff --git a/ServiceRegistration/srp-parse.c b/ServiceRegistration/srp-parse.c
index 10f3d7b..57c219c 100644
--- a/ServiceRegistration/srp-parse.c
+++ b/ServiceRegistration/srp-parse.c
@@ -48,40 +48,54 @@
 // Free the data structures into which the SRP update was parsed.   The pointers to the various DNS objects that these
 // structures point to are owned by the parsed DNS message, and so these do not need to be freed here.
 void
-srp_update_free_parts(service_instance_t *service_instances, service_instance_t *added_instances,
-                      service_t *services, delete_t *removes, dns_host_description_t *host_description)
+srp_parse_client_updates_free_(client_update_t *messages, const char *file, int line)
 {
-    service_instance_t *sip;
-    service_t *sp;
-    delete_t *dp;
+    client_update_t *message = messages;
+    while (message) {
+        INFO("%p at " PUB_S_SRP ":%d", message, file, line);
+        client_update_t *next_message = message->next;
 
-    for (sip = service_instances; sip; ) {
-        service_instance_t *next = sip->next;
-        free(sip);
-        sip = next;
-    }
-    for (sip = added_instances; sip; ) {
-        service_instance_t *next = sip->next;
-        free(sip);
-        sip = next;
-    }
-    for (sp = services; sp; ) {
-        service_t *next = sp->next;
-        free(sp);
-        sp = next;
-    }
-    for (dp = removes; dp != NULL; ) {
-        delete_t *next = dp->next;
-        free(dp);
-        dp = next;
-    }
-    if (host_description != NULL) {
-        host_addr_t *host_addr, *next;
-        for (host_addr = host_description->addrs; host_addr; host_addr = next) {
-            next = host_addr->next;
-            free(host_addr);
+        for (service_instance_t *sip = message->instances; sip; ) {
+            service_instance_t *next = sip->next;
+            free(sip);
+            sip = next;
         }
-        free(host_description);
+        for (service_t *sp = message->services; sp; ) {
+            service_t *next = sp->next;
+            free(sp);
+            sp = next;
+        }
+        for (delete_t *dp = message->removes; dp != NULL; ) {
+            delete_t *next = dp->next;
+            free(dp);
+            dp = next;
+        }
+        if (message->host != NULL) {
+            host_addr_t *host_addr, *next;
+            for (host_addr = message->host->addrs; host_addr; host_addr = next) {
+                next = host_addr->next;
+                free(host_addr);
+            }
+            free(message->host);
+        }
+        if (message->parsed_message != NULL) {
+            dns_message_free(message->parsed_message);
+        }
+        if (message->message != NULL) {
+            ioloop_message_release(message->message);
+        }
+#if SRP_FEATURE_REPLICATION
+        if (message->srpl_connection != NULL) {
+            srpl_connection_release(message->srpl_connection);
+            message->srpl_connection = NULL;
+        }
+#endif
+        if (message->connection != NULL) {
+            ioloop_comm_release(message->connection);
+            message->connection = NULL;
+        }
+        free(message);
+        message = next_message;
     }
 }
 
@@ -203,9 +217,42 @@
     return NULL;
 }
 
-bool
-srp_evaluate(comm_t *connection, srp_server_t *server_state, srpl_connection_t *srpl_connection,
-             dns_message_t *message, message_t *raw_message)
+static bool
+srp_parse_lease_times(dns_message_t *message, uint32_t *r_lease, uint32_t *r_key_lease)
+{
+    // Get the lease time. We need this to differentiate between a mass host deletion and an add.
+    uint32_t lease_time = 3600;
+    uint32_t key_lease_time = 604800;
+    bool found_lease = false;
+    for (dns_edns0_t *edns0 = message->edns0; edns0; edns0 = edns0->next) {
+        if (edns0->type == dns_opt_update_lease) {
+            unsigned off = 0;
+            if (edns0->length != 4 && edns0->length != 8) {
+                ERROR("edns0 update-lease option length bogus: %d", edns0->length);
+                return false;
+            }
+            dns_u32_parse(edns0->data, edns0->length, &off, &lease_time);
+            if (edns0->length == 8) {
+                dns_u32_parse(edns0->data, edns0->length, &off, &key_lease_time);
+            } else {
+                key_lease_time = 7 * lease_time;
+            }
+            found_lease = true;
+        }
+    }
+
+    // Update-lease option is required for SRP.
+    if (!found_lease) {
+        ERROR("no update-lease edns0 option found in supposed SRP update");
+        return false;
+    }
+    *r_lease = lease_time;
+    *r_key_lease = key_lease_time;
+    return true;
+}
+
+client_update_t *
+srp_evaluate(const char *remote_name, dns_message_t **in_parsed_message, message_t *raw_message, int index)
 {
     unsigned i;
     dns_host_description_t *host_description = NULL;
@@ -213,55 +260,86 @@
     service_instance_t *service_instances = NULL, *sip, **sipp = &service_instances;
     service_t *services = NULL, *sp, **spp = &services;
     dns_rr_t *signature;
-    bool ret = false;
     struct timeval now;
-    dns_name_t *update_zone, *replacement_zone;
+    dns_name_t *update_zone = NULL, *replacement_zone = NULL;
     dns_name_t *uzp;
     dns_rr_t *key = NULL;
     dns_rr_t **keys = NULL;
     unsigned num_keys = 0;
     unsigned max_keys = 1;
     bool found_key = false;
-    uint32_t lease_time, key_lease_time, serial_number;
-    dns_edns0_t *edns0;
-    int rcode = dns_rcode_servfail;
-    bool found_lease = false;
-    bool found_serial = false;
-    char namebuf1[DNS_MAX_NAME_SIZE], namebuf2[DNS_MAX_NAME_SIZE];
+#if SRP_PARSE_DEBUG_VERBOSE
+    char namebuf2[DNS_MAX_NAME_SIZE];
+#endif
+    dns_message_t *message;
 
 
+    client_update_t *ret = calloc(1, sizeof(*ret));
+    if (ret == NULL) {
+        ERROR("no memory for client update");
+        return NULL;
+    }
+
+    ret->drop = false;
+    ret->rcode = dns_rcode_servfail;
+
+    if (in_parsed_message != NULL) {
+        ret->parsed_message = *in_parsed_message;
+        *in_parsed_message = NULL;
+    } else {
+        // Parse the UPDATE message.
+        if (!dns_wire_parse(&ret->parsed_message, &raw_message->wire, raw_message->length, false)) {
+            ERROR("dns_wire_parse failed.");
+            goto out;
+        }
+    }
+    ret->message = raw_message;
+    RETAIN_HERE(ret->message, message);
+    message = ret->parsed_message; // For brevity
+
+    if (!srp_parse_lease_times(ret->parsed_message, &ret->host_lease, &ret->key_lease)) {
+        ret->rcode = dns_rcode_formerr;
+        goto out;
+    }
+
     // Update requires a single SOA record as the question
     if (message->qdcount != 1) {
         ERROR("update received with qdcount > 1");
-        return false;
+        ret->rcode = dns_rcode_formerr;
+        return ret;
     }
 
     // Update should contain zero answers.
     if (message->ancount != 0) {
         ERROR("update received with ancount > 0");
-        return false;
+        ret->rcode = dns_rcode_formerr;
+        return ret;
     }
 
     if (message->questions[0].type != dns_rrtype_soa) {
         ERROR("update received with rrtype %d instead of SOA in question section.",
               message->questions[0].type);
-        return false;
+        ret->rcode = dns_rcode_formerr;
+        return ret;
     }
 
-
-    if (srpl_connection == NULL) {
+    if (remote_name == NULL) {
         raw_message->received_time = srp_time();
     }
 
     update_zone = message->questions[0].name;
     if (service_update_zone != NULL && dns_names_equal_text(update_zone, "default.service.arpa.")) {
+#if SRP_PARSE_DEBUG_VERBOSE
         INFO(PRI_S_SRP " is in default.service.arpa, using replacement zone: " PUB_S_SRP,
              dns_name_print(update_zone, namebuf2, sizeof(namebuf2)),
              dns_name_print(service_update_zone, namebuf1, sizeof(namebuf1)));
+#endif
         replacement_zone = service_update_zone;
     } else {
+#if SRP_PARSE_DEBUG_VERBOSE
         INFO(PRI_S_SRP " is not in default.service.arpa, or no replacement zone (%p)",
              dns_name_print(update_zone, namebuf2, sizeof(namebuf2)), service_update_zone);
+#endif
         replacement_zone = NULL;
     }
 
@@ -275,7 +353,7 @@
         if (rr->type == dns_rrtype_any && rr->qclass == dns_qclass_any && rr->ttl == 0) {
             int status = make_delete(&deletes, NULL, rr, update_zone);
             if (status != dns_rcode_noerror) {
-                rcode = status;
+                ret->rcode = status;
                 goto out;
             }
         }
@@ -327,7 +405,7 @@
                     DNS_NAME_GEN_SRP(rr->name, name_buf);
                     ERROR("ADD for hostname " PRI_DNS_NAME_SRP " without a preceding delete.",
                           DNS_NAME_PARAM_SRP(rr->name, name_buf));
-                    rcode = dns_rcode_formerr;
+                    ret->rcode = dns_rcode_formerr;
                     goto out;
                 }
                 host_description->delete = dp;
@@ -354,7 +432,7 @@
                 DNS_NAME_GEN_SRP(rr->name, name_buf);
                 ERROR("ADD for service instance not preceded by delete: " PRI_DNS_NAME_SRP,
                       DNS_NAME_PARAM_SRP(rr->name, name_buf));
-                rcode = dns_rcode_formerr;
+                ret->rcode = dns_rcode_formerr;
                 goto out;
             }
             for (sip = service_instances; sip; sip = sip->next) {
@@ -380,7 +458,7 @@
                     DNS_NAME_GEN_SRP(rr->name, name_buf);
                     ERROR("more than one SRV rr received for service instance: " PRI_DNS_NAME_SRP,
                           DNS_NAME_PARAM_SRP(rr->name, name_buf));
-                    rcode = dns_rcode_formerr;
+                    ret->rcode = dns_rcode_formerr;
                     goto out;
                 }
                 sip->srv = rr;
@@ -389,7 +467,7 @@
                     DNS_NAME_GEN_SRP(rr->name, name_buf);
                     ERROR("more than one TXT rr received for service instance: " PRI_DNS_NAME_SRP,
                           DNS_NAME_PARAM_SRP(rr->name, name_buf));
-                    rcode = dns_rcode_formerr;
+                    ret->rcode = dns_rcode_formerr;
                     goto out;
                 }
                 sip->txt = rr;
@@ -423,7 +501,7 @@
                     ERROR("service subtype " PRI_DNS_NAME_SRP " for " PRI_DNS_NAME_SRP
                           " has no preceding base type ", DNS_NAME_PARAM_SRP(rr->name, name_buf),
                           DNS_NAME_PARAM_SRP(rr->data.ptr.name, target_name_buf));
-                    rcode = dns_rcode_formerr;
+                    ret->rcode = dns_rcode_formerr;
                     goto out;
                 }
             }
@@ -432,7 +510,7 @@
             if (rr->qclass == dns_qclass_none && rr->ttl == 0) {
                 int status = make_delete(&deletes, &dp, rr, update_zone);
                 if (status != dns_rcode_noerror) {
-                    rcode = status;
+                    ret->rcode = status;
                     goto out;
                 }
             } else {
@@ -460,7 +538,7 @@
                     ERROR("service name " PRI_DNS_NAME_SRP " for " PRI_DNS_NAME_SRP
                           " is not in the update zone", DNS_NAME_PARAM_SRP(rr->name, name_buf),
                           DNS_NAME_PARAM_SRP(rr->data.ptr.name, data_name_buf));
-                    rcode = dns_rcode_formerr;
+                    ret->rcode = dns_rcode_formerr;
                     goto out;
                 }
             }
@@ -471,7 +549,7 @@
             DNS_NAME_GEN_SRP(rr->name, name_buf);
             ERROR("unexpected rrtype %d on " PRI_DNS_NAME_SRP " in update.", rr->type,
                   DNS_NAME_PARAM_SRP(rr->name, name_buf));
-            rcode = dns_rcode_formerr;
+            ret->rcode = dns_rcode_formerr;
             goto out;
         }
     }
@@ -479,40 +557,9 @@
     // Now that we've scanned the whole update, do the consistency checks for updates that might
     // not have come in order.
 
-    // Get the lease time. We need this to differentiate between a mass host deletion and an add.
-    lease_time = 3600;
-    key_lease_time = 604800;
-    serial_number = 0;
-    for (edns0 = message->edns0; edns0; edns0 = edns0->next) {
-        if (edns0->type == dns_opt_update_lease) {
-            unsigned off = 0;
-            if (edns0->length != 4 && edns0->length != 8) {
-                ERROR("edns0 update-lease option length bogus: %d", edns0->length);
-                rcode = dns_rcode_formerr;
-                goto out;
-            }
-            dns_u32_parse(edns0->data, edns0->length, &off, &lease_time);
-            if (edns0->length == 8) {
-                dns_u32_parse(edns0->data, edns0->length, &off, &key_lease_time);
-            } else {
-                key_lease_time = 7 * lease_time;
-            }
-            found_lease = true;
-        } else if (edns0->type == dns_opt_srp_serial) {
-            unsigned off = 0;
-            if (edns0->length != 4) {
-                ERROR("edns0 srp serial number length bogus: %d", edns0->length);
-                rcode = dns_rcode_formerr;
-                goto out;
-            }
-            dns_u32_parse(edns0->data, edns0->length, &off, &serial_number);
-            found_serial = true;
-        }
-    }
-
-    // If we don't yet have a host description, but this is a delete of the entire host registration (lease_time == 0) and
+    // If we don't yet have a host description, but this is a delete of the entire host registration (host_lease == 0) and
     // we do have a delete record and a key record for the host, create a host description with no addresses here.
-    if (host_description == NULL && lease_time == 0) {
+    if (host_description == NULL && ret->host_lease == 0) {
         // If we get here and we have a key, then that suggests that this SRP update is a host remove with a KEY RR to
         // authenticate it (and possibly leave behind).
         if (key != NULL) {
@@ -532,7 +579,7 @@
     // Make sure there's a host description.
     if (!host_description) {
         ERROR("SRP update does not include a host description.");
-        rcode = dns_rcode_formerr;
+        ret->rcode = dns_rcode_formerr;
         goto out;
     }
 
@@ -558,7 +605,7 @@
             DNS_NAME_GEN_SRP(sp->rr->name, name_buf);
             ERROR("service points to an instance that's not included: " PRI_DNS_NAME_SRP,
                   DNS_NAME_PARAM_SRP(sp->rr->name, name_buf));
-            rcode = dns_rcode_formerr;
+            ret->rcode = dns_rcode_formerr;
             goto out;
         }
     }
@@ -569,7 +616,7 @@
             DNS_NAME_GEN_SRP(sip->name, name_buf);
             ERROR("service instance update for " PRI_DNS_NAME_SRP
                   " is not referenced by a service update.", DNS_NAME_PARAM_SRP(sip->name, name_buf));
-            rcode = dns_rcode_formerr;
+            ret->rcode = dns_rcode_formerr;
             goto out;
         }
 
@@ -586,16 +633,16 @@
         DNS_NAME_GEN_SRP(host_description->name, name_buf);
         ERROR("host description " PRI_DNS_NAME_SRP " is not referenced by any service instances.",
               DNS_NAME_PARAM_SRP(host_description->name, name_buf));
-        rcode = dns_rcode_formerr;
+        ret->rcode = dns_rcode_formerr;
         goto out;
     }
 
     // Make sure the host description has at least one address record, unless we're deleting the host.
-    if (host_description->addrs == NULL && host_description->num_instances != 0 && lease_time != 0) {
+    if (host_description->addrs == NULL && host_description->num_instances != 0 && ret->host_lease != 0) {
         DNS_NAME_GEN_SRP(host_description->name, name_buf);
         ERROR("host description " PRI_DNS_NAME_SRP " doesn't contain any IP addresses, but services are being added.",
               DNS_NAME_PARAM_SRP(host_description->name, name_buf));
-        rcode = dns_rcode_formerr;
+        ret->rcode = dns_rcode_formerr;
         goto out;
     }
 #endif
@@ -605,7 +652,7 @@
         if (i > 0) {
             if (!dns_keys_rdata_equal(key, keys[i])) {
                 ERROR("more than one key presented");
-                rcode = dns_rcode_formerr;
+                ret->rcode = dns_rcode_formerr;
                 goto out;
             }
             // This is a hack so that if num_keys == 1, we don't have to allocate keys[].
@@ -629,7 +676,7 @@
             DNS_NAME_GEN_SRP(key->name, key_name_buf);
             ERROR("key present for name " PRI_DNS_NAME_SRP
                   " which is neither a host nor an instance name.", DNS_NAME_PARAM_SRP(key->name, key_name_buf));
-            rcode = dns_rcode_formerr;
+            ret->rcode = dns_rcode_formerr;
             goto out;
         }
     }
@@ -643,7 +690,7 @@
         DNS_NAME_GEN_SRP(host_description->name, host_name_buf);
         ERROR("host description " PRI_DNS_NAME_SRP " doesn't contain a key.",
               DNS_NAME_PARAM_SRP(host_description->name, host_name_buf));
-        rcode = dns_rcode_formerr;
+        ret->rcode = dns_rcode_formerr;
         goto out;
     }
 
@@ -672,13 +719,13 @@
     // will either accept or reject it.
     if (message->arcount < 1) {
         ERROR("signature not present");
-        rcode = dns_rcode_formerr;
+        ret->rcode = dns_rcode_formerr;
         goto out;
     }
     signature = &message->additional[message->arcount -1];
     if (signature->type != dns_rrtype_sig) {
         ERROR("signature is not at the end or is not present");
-        rcode = dns_rcode_formerr;
+        ret->rcode = dns_rcode_formerr;
         goto out;
     }
 
@@ -690,7 +737,7 @@
         ERROR("signer " PRI_DNS_NAME_SRP " doesn't match host " PRI_DNS_NAME_SRP,
               DNS_NAME_PARAM_SRP(signature->data.sig.signer, signer_name_buf),
               DNS_NAME_PARAM_SRP(host_description->name, host_name_buf));
-        rcode = dns_rcode_formerr;
+        ret->rcode = dns_rcode_formerr;
         goto out;
     }
 
@@ -735,6 +782,11 @@
             replace_zone_name(&dp->name, dp->zone, replacement_zone);
         }
 
+        // We also need to update the names of removes.
+        for (dp = removes; dp; dp = dp->next) {
+            replace_zone_name(&dp->name, dp->zone, replacement_zone);
+        }
+
         // All services have PTR records, which point to names.   Both the service name and the
         // PTR name have to be fixed up.
         for (sp = services; sp; sp = sp->next) {
@@ -744,7 +796,7 @@
             // if condition should always be false.
             if (uzp == NULL) {
                 ERROR("service PTR record zone match fail!!");
-                rcode = dns_rcode_formerr;
+                ret->rcode = dns_rcode_formerr;
                 goto out;
             }
             replace_zone_name(&sp->rr->data.ptr.name, uzp, replacement_zone);
@@ -759,7 +811,7 @@
             // if condition should always be false.
             if (uzp == NULL) {
                 ERROR("service instance SRV record zone match fail!!");
-                rcode = dns_rcode_formerr;
+                ret->rcode = dns_rcode_formerr;
                 goto out;
             }
             replace_zone_name(&sip->srv->data.srv.name, uzp, replacement_zone);
@@ -779,42 +831,17 @@
         srp_format_time_offset(time_buf, sizeof(time_buf), srp_time() - raw_message->received_time);
     }
 
-    INFO("update for " PRI_DNS_NAME_SRP " xid %x validates, lease time %d%s, receive_time "
-         PUB_S_SRP ", remote " PRI_S_SRP ".",
-         DNS_NAME_PARAM_SRP(host_description->name, host_description_name_buf), raw_message->wire.id, lease_time,
-         found_lease ? " (found)" : "", time_buf, srpl_connection == NULL ? "(none)" : srpl_connection->name);
-    rcode = dns_rcode_noerror;
-    ret = srp_update_start(connection, server_state, srpl_connection, message, raw_message, host_description,
-                           service_instances, services, removes,
-                           replacement_zone == NULL ? update_zone : replacement_zone,
-                           lease_time, key_lease_time, serial_number, found_serial);
-    if (ret) {
-        goto success;
-    }
-    ERROR("update start failed");
+    INFO("update for " PRI_DNS_NAME_SRP " #%d, xid %x validates, key lease %d, host lease %d, message lease %d, receive_time "
+         PUB_S_SRP ", remote " PRI_S_SRP " -> %p.",
+         DNS_NAME_PARAM_SRP(host_description->name, host_description_name_buf), index, raw_message->wire.id,
+         ret->key_lease, ret->host_lease, raw_message->lease, time_buf, remote_name == NULL ? "(none)" : remote_name, ret);
+    ret->rcode = dns_rcode_noerror;
     goto out;
 
 badsig:
-    if (srpl_connection == NULL) {
-        // True means it was intended for us, and shouldn't be forwarded.
-        ret = true;
-    } else {
-        // For SRP replication, we need to return false when the signature check fails.
-        ret = false;
-    }
-    // We're not actually going to return this; it simply indicates that we aren't sending a fail response.
-    rcode = dns_rcode_noerror;
-    // Because we're saying this is ours, we have to free the parsed message.
-    dns_message_free(message);
+    ret->drop = true;
 
 out:
-    // free everything we allocated but (it turns out) aren't going to use
-    if (keys != NULL) {
-        free(keys);
-    }
-    srp_update_free_parts(service_instances, NULL, services, removes, host_description);
-
-success:
     // No matter how we get out of this, we free the delete structures that weren't dangling removes,
     // because they are not used to do the update.
     for (dp = deletes; dp; ) {
@@ -823,66 +850,180 @@
         dp = next;
     }
 
-    if (ret == true && rcode != dns_rcode_noerror && srpl_connection == NULL) {
-        if (connection != NULL) {
-            send_fail_response(connection, raw_message, rcode);
-        }
-    }
+    // We always return what we got, even if we failed
+    ret->host = host_description;
+    ret->instances = service_instances;
+    ret->services = services;
+    ret->removes = removes;
+    ret->update_zone = replacement_zone == NULL ? update_zone : replacement_zone;
     return ret;
 }
 
 bool
-srp_dns_evaluate(comm_t *connection, srp_server_t *server_state, srpl_connection_t *srpl_connection, message_t *message)
+srp_dns_evaluate(comm_t *connection, srp_server_t *server_state, message_t *message, dns_message_t **p_parsed_message)
 {
-    dns_message_t *parsed_message;
+    bool continuing = false;
+    client_update_t *update = NULL;
 
     // Drop incoming responses--we're a server, so we only accept queries.
     if (dns_qr_get(&message->wire) == dns_qr_response) {
-        ERROR("dns_evaluate: received a message that was a DNS response: %d", dns_opcode_get(&message->wire));
-        return false;
+        ERROR("received a message that was a DNS response: %d", dns_opcode_get(&message->wire));
+        goto out;
     }
 
-    // Forward incoming messages that are queries but not updates.
-    // XXX do this later--for now we operate only as a translator, not a proxy.
+    // Forward incoming messages that are queries to the dnssd-proxy code.
+    if (dns_opcode_get(&message->wire) == dns_opcode_query)
+    {
+        dns_proxy_input_for_server(connection, server_state, message, NULL);
+        goto out;
+    }
+
     if (dns_opcode_get(&message->wire) != dns_opcode_update) {
-        if (connection != NULL) {
-            send_fail_response(connection, message, dns_rcode_refused);
-        }
-        ERROR("dns_evaluate: received a message that was not a DNS update: %d", dns_opcode_get(&message->wire));
-        return false;
-    }
-
-    // Parse the UPDATE message.
-    if (!dns_wire_parse(&parsed_message, &message->wire, message->length, false)) {
-        if (connection != NULL) {
-            send_fail_response(connection, message, dns_rcode_servfail);
-        }
-        ERROR("dns_wire_parse failed.");
-        return false;
+        // dns_forward(connection)
+        send_fail_response(connection, message, dns_rcode_refused);
+        ERROR("received a message that was not a DNS update: %d", dns_opcode_get(&message->wire));
+        goto out;
     }
 
     // We need the wire message to validate the signature...
-    if (!srp_evaluate(connection, server_state, srpl_connection, parsed_message, message)) {
-        // For srpl connections, a false return value means the update failed. For regular SRP updates,
-        // a false return value means that the update was not consumed.
-        if (!srpl_connection) {
-            // The message wasn't invalid, but wasn't an SRP message.
-            dns_message_free(parsed_message);
-            // dns_forward(connection)
-            if (connection != NULL) {
-                send_fail_response(connection, message, dns_rcode_refused);
+    update = srp_evaluate(NULL, p_parsed_message, message, 0);
+    if (update == NULL) {
+        send_fail_response(connection, message, dns_rcode_servfail);
+        goto out;
+    }
+    if (update->rcode != dns_rcode_noerror) {
+        if (!update->drop) {
+            send_fail_response(connection, message, update->rcode);
+        }
+        goto out;
+    }
+
+    update->connection = connection;
+    ioloop_comm_retain(update->connection);
+    update->server_state = server_state;
+
+    continuing = srp_update_start(update);
+    goto good;
+out:
+    srp_parse_client_updates_free(update);
+good:
+    return continuing;
+}
+
+#if SRP_FEATURE_REPLICATION
+static bool
+srp_parse_eliminate_shadowed_updates(srp_server_t *server_state, client_update_t *new_message, client_update_t *old_message)
+{
+    (void)server_state;
+
+    // We only ever want the last host update.
+    old_message->skip_host_updates = true;
+
+    // Look for matching instances.
+    for (service_instance_t *old = old_message->instances; old != NULL; old = old->next) {
+        for (service_instance_t *new = new_message->instances; new != NULL; new = new->next) {
+            if (dns_names_equal(old->name, new->name)) {
+                old->skip_update = true;
             }
         }
-        return false;
+        for (delete_t *delete = new_message->removes; delete != NULL; delete = delete->next) {
+            DNS_NAME_GEN_SRP(old->name, old_name_buf);
+            DNS_NAME_GEN_SRP(delete->name, delete_name_buf);
+            INFO("old service " PRI_DNS_NAME_SRP ", delete " PRI_DNS_NAME_SRP,
+                 DNS_NAME_PARAM_SRP(old->name, old_name_buf), DNS_NAME_PARAM_SRP(delete->name, delete_name_buf));
+            if (dns_names_equal(old->name, delete->name)) {
+                old->skip_update = true;
+            }
+        }
     }
     return true;
 }
 
+// For SRP replication
+bool
+srp_parse_host_messages_evaluate(srp_server_t *UNUSED server_state, srpl_connection_t *srpl_connection,
+                                 message_t **messages, int num_messages)
+{
+    client_update_t *client_updates = NULL;
+    bool ret = false;
+
+    for (int i = 0; i < num_messages; i++) {
+        message_t *message = messages[i];
+
+        // Drop incoming responses--we're a server, so we only accept queries.
+        if (dns_qr_get(&message->wire) == dns_qr_response) {
+            ERROR("received a message that was a DNS response: %d", dns_opcode_get(&message->wire));
+            goto out;
+        }
+
+        // Forward incoming messages that are queries but not updates.
+        // XXX do this later--for now we operate only as a translator, not a proxy.
+        if (dns_opcode_get(&message->wire) != dns_opcode_update) {
+            ERROR("received a message that was not a DNS update: %d", dns_opcode_get(&message->wire));
+            goto out;
+        }
+
+        // We need the wire message to validate the signature...
+        INFO("evaluating message #%d from %s", i, srpl_connection->name);
+        client_update_t *update = srp_evaluate(srpl_connection->name, NULL, message, i);
+        if (update == NULL) {
+            goto out;
+        }
+        if (update->rcode != dns_rcode_noerror) {
+            update->next = client_updates;
+            client_updates = update;
+            goto out;
+        }
+        update->srpl_connection = srpl_connection;
+        srpl_connection_retain(update->srpl_connection);
+        update->server_state = server_state;
+        update->index = i;
+
+        // We build the list of messages so that message 0 winds up at the /end/ of the list; message 0 is
+        // the earliest message.
+        update->next = client_updates;
+        client_updates = update;
+    }
+
+    // Now that we've parsed and validated everything, eliminate earlier updates that are in the shadow of
+    // later updates.
+    // The list off of client_updates is ordered with most recent messages first, so what we want to do
+    // is, for each message on the list, see if any earlier messages update the same instance. If so, remove
+    // the earlier update, since it is out of date and could create a conflict if we tried to apply it.
+    // If we get an update that deletes the host, every update earlier than that is invalidated. It would
+    // be weird for us to get an update that is earlier than a full delete, but we could well get a full
+    // delete as the earliest recorded update.
+    for (client_update_t *em = client_updates; em != NULL; em = em->next) {
+        for (client_update_t *lem = em->next; lem != NULL; lem = lem->next) {
+            srp_parse_eliminate_shadowed_updates(server_state, em, lem);
+        }
+    }
+
+    // Now re-order the list oldest to newest.
+    client_update_t *current = client_updates, *next = NULL, *prev = NULL;
+    while (current != NULL) {
+        next = current->next;
+        current->next = prev;
+        prev = current;
+        current = next;
+    }
+    client_updates = prev;
+
+    // Now that we've eliminated shadowed updates, we can actually call srp_update_start.
+    ret = srp_update_start(client_updates);
+    goto good;
+out:
+    srp_parse_client_updates_free(client_updates);
+good:
+    return ret;
+}
+#endif
+
 void
 dns_input(comm_t *comm, message_t *message, void *context)
 {
-    (void)context;
-    srp_dns_evaluate(comm, context, NULL, message);
+    srp_server_t *server_state = context;
+    srp_dns_evaluate(comm, server_state, message, NULL);
 }
 
 struct srp_proxy_listener_state {
@@ -892,13 +1033,21 @@
 };
 
 comm_t *
-srp_proxy_listen(uint16_t *avoid_ports, int num_avoid_ports, ready_callback_t ready, cancel_callback_t cancel_callback,
+srp_proxy_listen(uint16_t *avoid_ports, int num_avoid_ports, const char *interface_name, ready_callback_t ready,
+                 cancel_callback_t cancel_callback, addr_t *address, finalize_callback_t context_release_callback,
                  void *context)
 {
+    unsigned ifindex = 0;
+    if (interface_name != NULL) {
+        ifindex = if_nametoindex(interface_name);
+        if (ifindex == 0) {
+            return false;
+        }
+    }
+
     // XXX UDP listeners should bind to interface addresses, not INADDR_ANY.
-    return ioloop_listener_create(false, false, avoid_ports,
-                                  num_avoid_ports, NULL, NULL, "SRP UDP listener", dns_input,
-                                  NULL, cancel_callback, ready, NULL, NULL, context);
+    return ioloop_listener_create(false, false, false, avoid_ports, num_avoid_ports, address, NULL, "SRP UDP listener",
+                                  dns_input, NULL, cancel_callback, ready, context_release_callback, NULL, ifindex, context);
 }
 
 void
diff --git a/ServiceRegistration/srp-proxy.h b/ServiceRegistration/srp-proxy.h
index b66e4a6..d41566a 100644
--- a/ServiceRegistration/srp-proxy.h
+++ b/ServiceRegistration/srp-proxy.h
@@ -23,31 +23,27 @@
 typedef struct srp_proxy_listener_state srp_proxy_listener_state_t;
 typedef struct srp_server_state srp_server_t;
 typedef struct srpl_connection srpl_connection_t;
+typedef struct client_update client_update_t;
 
 void srp_proxy_listener_cancel(srp_proxy_listener_state_t *NONNULL listener_state);
-comm_t *NULLABLE srp_proxy_listen(uint16_t *NULLABLE avoid_ports, int num_avoid_ports, ready_callback_t NULLABLE ready,
-                                  cancel_callback_t NULLABLE cancel, void *NONNULL context);
+comm_t *NULLABLE srp_proxy_listen(uint16_t *NULLABLE avoid_ports, int num_avoid_ports, const char *NULLABLE interface_name,
+                                  ready_callback_t NULLABLE ready, cancel_callback_t NULLABLE cancel,
+                                  addr_t *NULLABLE address, finalize_callback_t NULLABLE context_callback, void *NONNULL context);
 void srp_proxy_init(const char *NONNULL update_zone);
-bool srp_evaluate(comm_t *NONNULL comm, srp_server_t *NULLABLE server_state,
-                  srpl_connection_t *NULLABLE srpl_connection,
-                  dns_message_t *NONNULL message, message_t *NONNULL raw_message);
-bool srp_update_start(comm_t *NONNULL connection, srp_server_t *NULLABLE server_state,
-                      srpl_connection_t *NULLABLE srpl_connection,
-                      dns_message_t *NONNULL parsed_message, message_t *NONNULL raw_message,
-                      dns_host_description_t *NONNULL new_host, service_instance_t *NONNULL instances,
-                      service_t *NONNULL services, delete_t *NULLABLE removes, dns_name_t *NONNULL update_zone,
-                      uint32_t lease_time, uint32_t key_lease_time, uint32_t serial_number, bool found_serial);
-void srp_update_free_parts(service_instance_t *NULLABLE service_instances, service_instance_t *NULLABLE added_instances,
-                           service_t *NULLABLE services, delete_t *NULLABLE removes,
-                           dns_host_description_t *NULLABLE host_description);
-void srp_update_free(update_t *NONNULL update);
-
+client_update_t *NULLABLE srp_evaluate(const char *NULLABLE remote_name,
+                                       dns_message_t *NONNULL *NULLABLE in_parsed_message,
+                                       message_t *NONNULL raw_message, int index);
+bool srp_update_start(client_update_t *NONNULL client_update);
+#define srp_parse_client_updates_free(messages) srp_parse_client_updates_free_(messages, __FILE__, __LINE__);
+void srp_parse_client_updates_free_(client_update_t *NULLABLE messages, const char *NONNULL file, int line);
 
 // Provided
 void dns_input(comm_t *NONNULL comm, message_t *NONNULL message, void *NULLABLE context);
 void srp_mdns_flush(srp_server_t *NONNULL server_state);
-bool srp_dns_evaluate(comm_t *NULLABLE connection, srp_server_t *NULLABLE server_state,
-                      srpl_connection_t *NULLABLE srpl_connection, message_t *NONNULL message);
+bool srp_dns_evaluate(comm_t *NONNULL connection, srp_server_t *NULLABLE server_state,
+                      message_t *NONNULL message, dns_message_t *NONNULL *NULLABLE p_parsed_message);
+bool srp_parse_host_messages_evaluate(srp_server_t *NONNULL server_state, srpl_connection_t *NONNULL srpl_connection,
+                                      message_t *NONNULL *NONNULL messages, int num_messages);
 #endif // __SRP_PROXY_H
 
 // Local Variables:
diff --git a/ServiceRegistration/srp-replication.c b/ServiceRegistration/srp-replication.c
index dda7f8d..b752c55 100644
--- a/ServiceRegistration/srp-replication.c
+++ b/ServiceRegistration/srp-replication.c
@@ -53,11 +53,14 @@
 
 #if SRP_FEATURE_REPLICATION
 #include "srp-replication.h"
+#ifdef SRP_TEST_SERVER
+#include "test-packet.h"
+#include "test-srpl.h"
+#endif
+
 
 #define SRPL_CONNECTION_IS_CONNECTED(connection) ((connection)->state > srpl_state_connecting)
 
-static srpl_instance_t *unmatched_instances;
-
 #define srpl_event_content_type_set(event, content_type) \
     srpl_event_content_type_set_(event, content_type, __FILE__, __LINE__)
 static bool srpl_event_content_type_set_(srpl_event_t *event,
@@ -67,7 +70,6 @@
 static srpl_state_t srpl_connection_drop_state(srpl_instance_t *instance, srpl_connection_t *srpl_connection);
 static void srpl_disconnect(srpl_connection_t *srpl_connection);
 static void srpl_connection_discontinue(srpl_connection_t *srpl_connection);
-static void srpl_connection_next_state(srpl_connection_t *srpl_connection, srpl_state_t state);
 static void srpl_event_initialize(srpl_event_t *event, srpl_event_type_t event_type);
 static void srpl_event_deliver(srpl_connection_t *srpl_connection, srpl_event_t *event);
 static void srpl_domain_advertise(srpl_domain_t *domain);
@@ -77,7 +79,7 @@
 static void srpl_instance_reconnect_callback(void *context);
 static bool srpl_domain_browse_start(srpl_domain_t *domain);
 static const char *srpl_state_name(srpl_state_t state);
-static bool srpl_can_transition_to_routine_state(const srpl_domain_t *domain);
+static bool srpl_can_transition_to_routine_state(srpl_domain_t *domain);
 static void srpl_transition_to_routine_state(srpl_domain_t *domain);
 static void srpl_message_sent(srpl_connection_t *srpl_connection);
 static srpl_state_t srpl_connection_schedule_reconnect_event(srpl_connection_t *srpl_connection, uint32_t when);
@@ -85,8 +87,15 @@
 static void srpl_instance_services_discontinue(srpl_instance_t *instance);
 static void srpl_instance_service_discontinue_timeout(void *context);
 static void srpl_maybe_sync_or_transition(srpl_domain_t *domain);
+static int srpl_dataset_id_compare(uint64_t id1, uint64_t id2);
+static void srpl_state_transition_by_dataset_id(srpl_domain_t *domain, srpl_instance_t *instance);
 
 #define EQUI_DISTANCE64 (int64_t)0x8000000000000000
+#define MAX_ADDITIONAL_HOST_MESSAGES 32
+#define SRPL_STATE_TIMEOUT (30 * 1000) // state timeout in milliseconds
+#define SECONDS_IN_MINUTE  60
+#define SECONDS_IN_HOUR    (60 * 60)
+#define SECONDS_IN_DAY     (24 * SECONDS_IN_HOUR)
 
 #ifdef DEBUG
 #define STATE_DEBUGGING_ABORT() abort();
@@ -265,7 +274,7 @@
 
     if (errorCode != kDNSServiceErr_NoError) {
         ERROR("address resolution for " PRI_S_SRP " failed with %d", fullname, errorCode);
-        address->change_callback(address->context, NULL, false, errorCode);
+        address->change_callback(address->context, NULL, false, flags & kDNSServiceFlagsMoreComing, errorCode);
         return;
     }
     if (rrclass != dns_qclass_in || ((rrtype != dns_rrtype_a || rdlen != 4) &&
@@ -336,7 +345,7 @@
         address->addresses[i] = addr;
         address->address_interface[i] = interfaceIndex;
         address->num_addresses++;
-        address->change_callback(address->context, &address->addresses[i], true, kDNSServiceErr_NoError);
+        address->change_callback(address->context, &address->addresses[i], true, flags & kDNSServiceFlagsMoreComing, kDNSServiceErr_NoError);
     } else {
         if (i == j) {
             ADDR_NAME_LOGGER(ERROR, &addr, "Remove for unknown address ", " received for ", " index ",
@@ -344,7 +353,7 @@
             return;
         } else {
             address->num_addresses--;
-            address->change_callback(address->context, &addr, false, kDNSServiceErr_NoError);
+            address->change_callback(address->context, &addr, false, flags & kDNSServiceFlagsMoreComing, kDNSServiceErr_NoError);
         }
     }
 }
@@ -700,12 +709,19 @@
     if (srpl_connection->connection != NULL) {
         ioloop_comm_release(srpl_connection->connection);
         srpl_connection->connection = NULL;
+        gettimeofday(&srpl_connection->connection_null_time, NULL);
+        srpl_connection->connection_null_reason = "finalize"; // obvsly should never see this!
     }
     if (srpl_connection->reconnect_wakeup != NULL) {
         ioloop_cancel_wake_event(srpl_connection->reconnect_wakeup);
         ioloop_wakeup_release(srpl_connection->reconnect_wakeup);
         srpl_connection->reconnect_wakeup = NULL;
     }
+    if (srpl_connection->state_timeout != NULL) {
+        ioloop_cancel_wake_event(srpl_connection->state_timeout);
+        ioloop_wakeup_release(srpl_connection->state_timeout);
+        srpl_connection->state_timeout = NULL;
+    }
     if (srpl_connection->keepalive_send_wakeup != NULL) {
         ioloop_cancel_wake_event(srpl_connection->keepalive_send_wakeup);
         ioloop_wakeup_release(srpl_connection->keepalive_send_wakeup);
@@ -716,6 +732,7 @@
         ioloop_wakeup_release(srpl_connection->keepalive_receive_wakeup);
         srpl_connection->keepalive_receive_wakeup = NULL;
     }
+    srpl_host_update_parts_free(&srpl_connection->stashed_host);
     free(srpl_connection->name);
     srpl_connection_reset(srpl_connection);
     free(srpl_connection);
@@ -733,20 +750,16 @@
     RETAIN(srpl_connection, srpl_connection);
 }
 
-static srpl_connection_t *
+srpl_connection_t *
 srpl_connection_create(srpl_instance_t *instance, bool outgoing)
 {
     srpl_connection_t *srpl_connection = calloc(1, sizeof (*srpl_connection)), *ret = NULL;
-    size_t srpl_connection_name_length;
     if (srpl_connection == NULL) {
         goto out;
     }
     RETAIN_HERE(srpl_connection, srpl_connection);
-    if (outgoing) {
-        srpl_connection_name_length = strlen(instance->instance_name) + 2;
-    } else {
-        srpl_connection_name_length = strlen(instance->instance_name) + 2;
-    }
+#define POINTER_TO_HEX_MAX_STRLEN 19 // 0x<...>
+    size_t srpl_connection_name_length = strlen(instance->instance_name) + 2 + POINTER_TO_HEX_MAX_STRLEN + 3;
     srpl_connection->name = malloc(srpl_connection_name_length);
     if (srpl_connection->name == NULL) {
         goto out;
@@ -760,10 +773,13 @@
         goto out;
     }
     srpl_connection->keepalive_interval = DEFAULT_KEEPALIVE_WAKEUP_EXPIRY / 2;
-    snprintf(srpl_connection->name, srpl_connection_name_length, "%s%s", outgoing ? ">" : "<", instance->instance_name);
+    snprintf(srpl_connection->name, srpl_connection_name_length, "%s%s (%p)", outgoing ? ">" : "<", instance->instance_name, srpl_connection);
     srpl_connection->is_server = !outgoing;
     srpl_connection->instance = instance;
     RETAIN_HERE(instance, srpl_instance);
+#ifdef SRP_TEST_SERVER
+    srpl_connection->state = srpl_state_test_event_intercept;
+#endif
     ret = srpl_connection;
     srpl_connection = NULL;
 out:
@@ -943,6 +959,24 @@
         service->srv_txn = NULL;
     }
     service->resolve_started = false;
+
+    // if all the services are discontinuing, we mark the instance to be discontinuing as well.
+    // discontinuing instance will be exluded when we check if the server has sync-ed on all the
+    // instances in order to move to the routine state and when we pick the winning dataset id
+    // from the discovered instances (i.e., discontinuing instance no longer qualifies for
+    // dataset_id election).
+    srpl_instance_t *instance = service->instance;
+    if (instance != NULL) {
+        srpl_instance_service_t *sp;
+        for(sp = instance->services; sp != NULL; sp = sp->next) {
+            if (!sp->discontinuing) {
+                break;
+            }
+        }
+        if (sp == NULL) {
+            instance->discontinuing = true;
+        }
+    }
     // It's not uncommon for a name to drop and then come back immediately. Wait 30s before
     // discontinuing the instance host.
     if (service->discontinue_timeout == NULL) {
@@ -963,9 +997,11 @@
 static void
 srpl_instance_discontinue(srpl_instance_t *instance)
 {
-    srpl_instance_service_t *service;
+    srpl_instance_service_t *service, *next;
     instance->discontinuing = true;
-    for (service = instance->services; service != NULL; service = service->next) {
+    for (service = instance->services; service != NULL; service = next) {
+        next = service->next;
+        service->num_copies = 0;
         srpl_instance_service_discontinue(service);
     }
 }
@@ -1047,8 +1083,9 @@
 
 // Stop service advertisement in the given domain.
 static void
-srpl_stop_srpl_domain_advertisement(srpl_domain_t *NONNULL domain)
+srpl_stop_domain_advertisement(srpl_domain_t *NONNULL domain)
 {
+    INFO("dropping advertisement for domain " PUB_S_SRP, domain->name);
     if (domain->srpl_advertise_txn != NULL) {
         ioloop_dnssd_txn_cancel(domain->srpl_advertise_txn);
         ioloop_dnssd_txn_release(domain->srpl_advertise_txn);
@@ -1062,11 +1099,7 @@
 {
     srpl_domain_t *domain;
     for (domain = server_state->srpl_domains; domain != NULL; domain = domain->next) {
-        if (domain->srpl_advertise_txn != NULL) {
-            ioloop_dnssd_txn_cancel(domain->srpl_advertise_txn);
-            ioloop_dnssd_txn_release(domain->srpl_advertise_txn);
-            domain->srpl_advertise_txn = NULL;
-        }
+        srpl_stop_domain_advertisement(domain);
     }
 }
 
@@ -1085,6 +1118,7 @@
 static void
 srpl_host_update_steal_parts(srpl_host_update_t *to, srpl_host_update_t *from)
 {
+    srpl_host_update_parts_free(to);
     *to = *from;
     from->hostname = NULL;
     from->messages = NULL;
@@ -1144,6 +1178,8 @@
     if (srpl_connection->connection != NULL && srpl_connection->connection == comm) {
         comm_t *connection = srpl_connection->connection;
         srpl_connection->connection = NULL;
+        gettimeofday(&srpl_connection->connection_null_time, NULL);
+        srpl_connection->connection_null_reason = "disconnected_callback";
         ioloop_comm_release(connection);
 
         if (srpl_connection->dso != NULL) {
@@ -1163,14 +1199,6 @@
     // Clear old data from connection.
     srpl_connection_reset(srpl_connection);
 
-    // If the connection is in the disconnect_wait state, deliver an event.
-    if (srpl_connection->state == srpl_state_disconnect_wait) {
-        srpl_event_t event;
-        srpl_event_initialize(&event, srpl_event_disconnected);
-        srpl_event_deliver(srpl_connection, &event);
-        goto out;
-    }
-
     domain = instance->domain;
     if (domain == NULL) {
         // If domain is NULL, instance has been discontinued.
@@ -1185,12 +1213,24 @@
             (instance->have_partner_id && domain->server_state != NULL &&
             domain->partner_id > instance->partner_id))
         {
+            // cancel reconnect_timeout if there's one scheduled.
+            if (instance->reconnect_timeout != NULL) {
+                ioloop_cancel_wake_event(instance->reconnect_timeout);
+            }
             INFO(PRI_S_SRP ": disconnect received, reconnecting.", srpl_connection->name);
             srpl_connection_next_state(srpl_connection, srpl_state_next_address_get);
             goto out;
         }
     }
 
+    // If the connection is in the disconnect_wait state, deliver an event.
+    if (srpl_connection->state == srpl_state_disconnect_wait) {
+        srpl_event_t event;
+        srpl_event_initialize(&event, srpl_event_disconnected);
+        srpl_event_deliver(srpl_connection, &event);
+        goto out;
+    }
+
     // If it's not our job to reconnect, we no longer need this connection. Release the reference
     // held by the instance (which'd cause the connection to be finalized).
     srpl_connection_next_state(srpl_connection, srpl_state_idle);
@@ -1206,10 +1246,23 @@
 static bool
 srpl_dso_message_setup(dso_state_t *dso, dso_message_t *state, dns_towire_state_t *towire, uint8_t *buffer,
                        size_t buffer_size, message_t *message, bool unidirectional, bool response, int rcode,
-                       uint16_t xid, void *context)
+                       uint16_t xid, srpl_connection_t *srpl_connection)
 {
     uint16_t send_xid = 0;
 
+    if (srpl_connection->connection == NULL) {
+        struct tm tm;
+        localtime_r(&srpl_connection->connection_null_time.tv_sec, &tm);
+        char tmoff = tm.tm_gmtoff > 0 ? '+' : '-';
+        long tzoff = tm.tm_gmtoff > 0 ? tm.tm_gmtoff : -tm.tm_gmtoff;
+        FAULT("sending a message on a nonexistent connection: " PUB_S_SRP " (%04d-%02d-%02d %02d:%02d:%02d.%06d%c%02ld%02ld)!",
+              srpl_connection->connection_null_reason == NULL
+              ? "no connection ever set" : srpl_connection->connection_null_reason,
+              tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
+              srpl_connection->connection_null_time.tv_usec, tmoff, tzoff / 3600, (tzoff / 60) % 60);
+        return false;
+    }
+
     if (buffer_size < DNS_HEADER_SIZE) {
         ERROR("internal: invalid buffer size %zd", buffer_size);
         return false;
@@ -1223,7 +1276,7 @@
         }
     }
     dso_make_message(state, buffer, buffer_size, dso, unidirectional, response,
-                     send_xid, rcode, context);
+                     send_xid, rcode, srpl_connection);
     memset(towire, 0, sizeof(*towire));
     towire->p = &buffer[DNS_HEADER_SIZE];
     towire->lim = towire->p + (buffer_size - DNS_HEADER_SIZE);
@@ -1235,7 +1288,7 @@
 srpl_connection_domain(srpl_connection_t *srpl_connection)
 {
     if (srpl_connection->instance == NULL) {
-        INFO("connection has no instance %p.", srpl_connection);
+        INFO("connection " PRI_S_SRP " (%p) has no instance.", srpl_connection->name, srpl_connection);
         return NULL;
     }
     return srpl_connection->instance->domain;
@@ -1326,6 +1379,22 @@
     if (domain == NULL) {
         return false;
     }
+#ifdef TEST_DSO_MESSAGE_SETUP_CONNECTION_NULL_FAULT
+    comm_t *connection = srpl_connection->connection;
+    const char *old_null_reason = srpl_connection->connection_null_reason;
+    struct timeval old_null_time = srpl_connection->connection_null_time;
+    srpl_connection->connection = NULL;
+    srpl_connection->connection_null_reason = "unit test";
+    gettimeofday(&srpl_connection->connection_null_time, NULL);
+    bool ret = srpl_dso_message_setup(srpl_connection->dso, &message, &towire, dsobuf, sizeof(dsobuf),
+                                      srpl_connection_message_get(srpl_connection), false, response, 0, 0, srpl_connection);
+    if (ret != false) {
+        FAULT("failed to detect null connection!");
+    }
+    srpl_connection->connection = connection;
+    srpl_connection->connection_null_reason = old_null_reason;
+    srpl_connection->connection_null_time = old_null_time;
+#endif
 
     if (!srpl_dso_message_setup(srpl_connection->dso, &message, &towire, dsobuf, sizeof(dsobuf),
                                 srpl_connection_message_get(srpl_connection), false, response, 0, 0, srpl_connection)) {
@@ -1501,7 +1570,8 @@
 static int
 srpl_message_compare(const void *v1, const void *v2)
 {
-    const message_t *m1 = v1, *m2 = v2;
+    const message_t *m1 = *(message_t**)v1;
+    const message_t *m2 = *(message_t**)v2;
     if (m1->received_time - m2->received_time < 0) {
         return -1;
     } else if(m1->received_time - m2->received_time > 0) {
@@ -1530,34 +1600,54 @@
     }
     iovec_count++;
     num_messages = 1;
+
+    time_t srpl_now = srp_time();
+
     if (SRPL_SUPPORTS(srpl_connection, SRPL_VARIATION_MULTI_HOST_MESSAGE)) {
-        for (int i = 0; i < host->instances->num; i++) {
+        int num_instances = host->instances == NULL ? 0 : host->instances->num;
+        for (int i = 0; i < num_instances; i++) {
             adv_instance_t *instance = host->instances->vec[i];
             if (instance != NULL) {
                 if (instance->message != NULL && instance->message != host->message && instance->message != NULL) {
                     num_messages++;
-                    iovec_count += 2;
-                    // Account for additional HostMessage TLV.
-                    dsobuf_length += DSO_TLV_HEADER_SIZE + sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint32_t);
                 }
             }
         }
-        messages = calloc(1, sizeof (*messages));
+        messages = calloc(num_messages, sizeof (*messages));
         if (messages == NULL) {
             INFO("no memory for message vector");
             goto out;
         }
         messages[0] = host->message;
         num_messages = 1;
-        for (int i = 0; i < host->instances->num; i++) {
+        for (int i = 0; i < num_instances; i++) {
             adv_instance_t *instance = host->instances->vec[i];
             if (instance != NULL) {
                 if (instance->message != NULL && instance->message != host->message && instance->message != NULL) {
                     messages[num_messages] = instance->message;
+                    num_messages++;
                 }
             }
         }
         qsort(messages, num_messages, sizeof(*messages), srpl_message_compare);
+        // eliminate the duplicate messages in the sorted array.
+        int nondup_pos = 0;
+        INFO("messages[0] = %p, received_time = %ld", messages[0], srpl_now - messages[0]->received_time);
+        for (int i = 1; i < num_messages; i++) {
+            if (messages[i] != messages[nondup_pos]) {
+                nondup_pos++;
+                messages[nondup_pos] = messages[i];
+                INFO("messages[%d] = messages[%d] (%p), received_time = %ld", nondup_pos, i, messages[i],
+                     srpl_now - messages[nondup_pos]->received_time);
+            }
+        }
+        num_messages = nondup_pos + 1;
+        // update iovec_count and dsobuf_length based on the number of messages.
+        // nondup_pos now is the position of the last unique message and it also
+        // is the number of extra host messages we have got in this SRPLHost message.
+        iovec_count += 2 * nondup_pos;
+        // Account for additional HostMessage TLV.
+        dsobuf_length += (DSO_TLV_HEADER_SIZE + sizeof(uint32_t) + sizeof(uint32_t) + sizeof(uint32_t)) * nondup_pos;
     }
     iov = calloc(iovec_count, sizeof(*iov));
     if (iov == NULL) {
@@ -1575,8 +1665,6 @@
         goto out;
     }
 
-    time_t srpl_now = srp_time();
-
     // For testing, make the update time really wrong so that signature validation fails. This will not actually
     // cause a failure unless the SRP requestor sends a time range, so really only useful for testing with the
     // mDNSResponder srp-client, not with e.g. a Thread client.
@@ -1613,8 +1701,8 @@
         iov[iov_cur].iov_base = &host->message->wire;
         iov_cur++;
     } else {
+        uint8_t *start = dsobuf;
         for (int i = 0; i < num_messages; i++) {
-            uint8_t *start = towire.p;
             dns_u16_to_wire(&towire, kDSOType_SRPLHostMessage);
             dns_u16_to_wire(&towire, 12 + messages[i]->length);
             dns_u32_to_wire(&towire, messages[i]->lease);
@@ -1626,6 +1714,7 @@
             iov[iov_cur].iov_len = messages[i]->length;
             iov[iov_cur].iov_base = &messages[i]->wire;
             iov_cur++;
+            start = towire.p;
         }
     }
 
@@ -1787,12 +1876,12 @@
         }
     }
     if (count < min_additls) {
-        ERROR(PRI_S_SRP ": not enough additional TLVs (%d) for " PUB_S_SRP ".",
-              srpl_connection->name, count, message_name);
+        ERROR(PRI_S_SRP ": not enough additional TLVs (%d < %d) for " PUB_S_SRP ".",
+              srpl_connection->name, count, min_additls, message_name);
         ret = false;
     } else if (count > max_additls) {
-        ERROR(PRI_S_SRP ": too many additional TLVs (%d) for " PUB_S_SRP ".",
-              srpl_connection->name, count, message_name);
+        ERROR(PRI_S_SRP ": too many additional TLVs (%d > %d) for " PUB_S_SRP ".",
+              srpl_connection->name, count, max_additls, message_name);
         ret = false;
     }
     return ret;
@@ -1811,6 +1900,12 @@
         ioloop_wakeup_release(srpl_connection->reconnect_wakeup);
         srpl_connection->reconnect_wakeup = NULL;
     }
+    // Cancel any outstanding state timeout event.
+    if (srpl_connection->state_timeout != NULL) {
+        ioloop_cancel_wake_event(srpl_connection->state_timeout);
+        ioloop_wakeup_release(srpl_connection->state_timeout);
+        srpl_connection->state_timeout = NULL;
+    }
     srpl_connection_reset(srpl_connection);
     srpl_connection_next_state(srpl_connection, srpl_state_disconnect);
 }
@@ -1923,6 +2018,7 @@
 
     srpl_connection_message_set(srpl_connection, message);
     if (!srpl_session_message_parse(srpl_connection, &event, dso, "SRPLSession message")) {
+        dns_name_free(event.content.session.domain_name);
         srpl_disconnect(srpl_connection);
         return;
     }
@@ -1939,6 +2035,7 @@
         srpl_disconnect(srpl_connection);
         return;
     }
+
     srpl_event_deliver(srpl_connection, &event);
     dns_name_free(event.content.session.domain_name);
 }
@@ -2099,8 +2196,8 @@
             const uint8_t *message_buffer;
             size_t message_length;
             if (update->rcode) {
-                message_buffer = buffer + 24; // lease, key-lease, time offset
-                message_length = length - 24;
+                message_buffer = buffer + 12; // lease, key-lease, time offset
+                message_length = length - 12;
             } else {
                 message_buffer = buffer;
                 message_length = length;
@@ -2115,6 +2212,7 @@
                       dns_u32_parse(buffer, length, offp, &message->key_lease) &&
                       dns_u32_parse(buffer, length, offp, &time_offset)))
                 {
+                    INFO("failed to parse lease, key_lease or time_offset");
                     return false;
                 }
                 message->received_time = srp_time() - time_offset;
@@ -2164,8 +2262,8 @@
         }
 
         if (!srpl_find_dso_additionals(srpl_connection, dso, additionals, required, multiple, names, indices,
-                                       num_additls, num_additls - 1, num_additls, "SRPLHost message",
-                                       &event.content.host_update, srpl_host_message_parse_in)) {
+                                       num_additls, num_additls - 1, num_additls + MAX_ADDITIONAL_HOST_MESSAGES,
+                                       "SRPLHost message", &event.content.host_update, srpl_host_message_parse_in)) {
             goto fail;
         }
         // update->max_messages can't be zero here, or we would have gotten a false return from
@@ -2177,22 +2275,26 @@
         }
         // Now that we know how many messages, we can copy them out.
         if (!srpl_find_dso_additionals(srpl_connection, dso, additionals, required, multiple, names, indices,
-                                       num_additls, num_additls - 1, num_additls, "SRPLHost message",
-                                       &event.content.host_update, srpl_host_message_parse_in)) {
+                                       num_additls, num_additls - 1, num_additls + MAX_ADDITIONAL_HOST_MESSAGES,
+                                       "SRPLHost message", &event.content.host_update, srpl_host_message_parse_in)) {
             goto fail;
         }
         DNS_NAME_GEN_SRP(event.content.host_update.hostname, hostname_buf);
-        INFO(PRI_S_SRP " received SRPLHost message %x for " PRI_DNS_NAME_SRP " server stable ID %" PRIx64,
-             srpl_connection->name, ntohs(message->wire.id),
-             DNS_NAME_PARAM_SRP(event.content.host_update.hostname, hostname_buf),
-             event.content.host_update.server_stable_id);
         if (!SRPL_SUPPORTS(srpl_connection, SRPL_VARIATION_MULTI_HOST_MESSAGE)) {
             time_t update_time = srp_time() - event.content.host_update.update_offset;
             event.content.host_update.messages[0]->received_time = update_time;
+            INFO(PRI_S_SRP " received SRPLHost message %x for " PRI_DNS_NAME_SRP " server stable ID %" PRIx64
+                 " update offset = %d", srpl_connection->name, ntohs(message->wire.id),
+                 DNS_NAME_PARAM_SRP(event.content.host_update.hostname, hostname_buf),
+                 event.content.host_update.server_stable_id, event.content.host_update.update_offset);
         } else {
             // Make sure times are sequential.
             time_t last_received_time = event.content.host_update.messages[0]->received_time;
-            for (int i = 0; i < event.content.host_update.num_messages; i++) {
+            INFO(PRI_S_SRP " received SRPLHost message %x for " PRI_DNS_NAME_SRP " server stable ID %" PRIx64
+                 " message 0 received time = %ld", srpl_connection->name, ntohs(message->wire.id),
+                 DNS_NAME_PARAM_SRP(event.content.host_update.hostname, hostname_buf),
+                 event.content.host_update.server_stable_id, srp_time() - last_received_time);
+            for (int i = 1; i < event.content.host_update.num_messages; i++) {
                 time_t cur_received_time = event.content.host_update.messages[i]->received_time;
                 if (cur_received_time - last_received_time <= 0) {
                     INFO(PRI_S_SRP
@@ -2201,6 +2303,7 @@
                          i, (long long)cur_received_time, i - 1, (long long)last_received_time);
                     goto fail_no_message;
                 }
+                INFO("message %d received time = %ld", i, cur_received_time);
                 last_received_time = cur_received_time;
             }
         }
@@ -2244,10 +2347,13 @@
 {
     if (srpl_connection->is_server) {
         int num_standby = 0;
-        for (srpl_instance_t *unmatched = unmatched_instances; unmatched != NULL; unmatched = unmatched->next) {
-            srpl_connection_t *unidentified = unmatched->connection;
-            if (unidentified != NULL && unidentified->state > srpl_state_session_evaluate) {
-                num_standby++;
+        srpl_domain_t *domain = srpl_connection_domain(srpl_connection);
+        if (domain != NULL && domain->server_state != NULL) {
+            for (srpl_instance_t *unmatched = domain->server_state->unmatched_instances; unmatched != NULL; unmatched = unmatched->next) {
+                srpl_connection_t *unidentified = unmatched->connection;
+                if (unidentified != NULL && unidentified->state > srpl_state_session_evaluate) {
+                    num_standby++;
+                }
             }
         }
         int new_interval = num_standby * DEFAULT_KEEPALIVE_WAKEUP_EXPIRY / 2;
@@ -2469,6 +2575,10 @@
 {
     srpl_connection_t *srpl_connection = context;
     srpl_instance_t *instance = srpl_connection->instance;
+    if (instance == NULL) {
+        INFO("datagram on connection " PRI_S_SRP " with no instance.", comm->name);
+        return;
+    }
 
     // If this is a DSO message, see if we have a session yet.
     switch(dns_opcode_get(&message->wire)) {
@@ -2503,6 +2613,8 @@
     } else {
         ioloop_comm_cancel(srpl_connection->connection);
         ioloop_comm_release(srpl_connection->connection);
+        gettimeofday(&srpl_connection->connection_null_time, NULL);
+        srpl_connection->connection_null_reason = "trigger_disconnect";
         srpl_connection->connection = NULL;
     }
 }
@@ -2511,17 +2623,19 @@
 srpl_connection_dso_life_cycle_callback(dso_life_cycle_t cycle, void *const context, dso_state_t *const dso)
 {
     if (cycle == dso_life_cycle_cancel) {
-        srpl_connection_t *connection = context;
-        INFO(PRI_S_SRP ": %p %p", connection->name, connection, dso);
-        if (connection->connection != NULL) {
-            ioloop_comm_cancel(connection->connection);
-            connection->connection->dso = NULL;
-            ioloop_comm_release(connection->connection);
-            connection->connection = NULL;
+        srpl_connection_t *srpl_connection = context;
+        INFO(PRI_S_SRP ": %p %p", srpl_connection->name, srpl_connection, dso);
+        if (srpl_connection->connection != NULL) {
+            ioloop_comm_cancel(srpl_connection->connection);
+            srpl_connection->connection->dso = NULL;
+            ioloop_comm_release(srpl_connection->connection);
+            gettimeofday(&srpl_connection->connection_null_time, NULL);
+            srpl_connection->connection_null_reason = "dso_life_cycle_callback";
+            srpl_connection->connection = NULL;
         }
-        srpl_connection_reset(connection);
-        connection->dso = NULL;
-        RELEASE_HERE(connection, srpl_connection);
+        srpl_connection_reset(srpl_connection);
+        srpl_connection->dso = NULL;
+        RELEASE_HERE(srpl_connection, srpl_connection);
         ioloop_run_async(srpl_connection_dso_cleanup, NULL);
         return true;
     }
@@ -2540,6 +2654,7 @@
         return;
     }
 
+
     srpl_connection->connection = connection;
     ioloop_comm_retain(srpl_connection->connection);
 
@@ -2573,7 +2688,7 @@
 
     // If we already have a connection with the remote partner, we replace it with the new connection.
     if (instance->connection != NULL) {
-        INFO(PRI_S_SRP "we already have a connection.", instance->instance_name);
+        INFO(PRI_S_SRP ": we already have a connection (%p).", instance->instance_name, old_connection);
         old_connection = instance->connection;
         RELEASE_HERE(old_connection->instance, srpl_instance);
         old_connection->instance = NULL;
@@ -2598,7 +2713,7 @@
     }
 
     // Check if an unmatched instance has been created for the same address
-    for (inp = &unmatched_instances; *inp != NULL; inp = &(*inp)->next) {
+    for (inp = &server_state->unmatched_instances; *inp != NULL; inp = &(*inp)->next) {
         if (!strcmp((*inp)->instance_name, instance_name)) {
             INFO("we already have an unmatched instance " PRI_S_SRP, instance_name);
             unmatched_instance = *inp;
@@ -2632,7 +2747,7 @@
             return;
         }
         // Find the end of the list and append the newly created instance.
-        for (inp = &unmatched_instances; *inp != NULL; inp = &(*inp)->next) {
+        for (inp = &server_state->unmatched_instances; *inp != NULL; inp = &(*inp)->next) {
         }
         *inp = unmatched_instance;
     }
@@ -2649,22 +2764,34 @@
     RETAIN_HERE(connection, srpl_connection); // for the function
     RELEASE_HERE(cur->connection, srpl_connection);
     cur->connection = NULL;
+    RELEASE_HERE(connection->instance, srpl_instance); // Get rid of the instance's reference to the connection
+    connection->instance = NULL;
 
     // Remove the currently associated instance from the unmatched_instances
     srpl_instance_t **sp = NULL;
 
     INFO("matched temporary instance " PRI_S_SRP " to instance " PRI_S_SRP " with partner_id %" PRIx64,
          cur->instance_name, instance->instance_name, instance->partner_id);
-    snprintf(connection->name, strlen(instance->instance_name) + 2, "%s%s", connection->is_server ? "<" : ">", instance->instance_name);
-    for (sp = &unmatched_instances; *sp; sp = &(*sp)->next) {
+    instance->version_mismatch = cur->version_mismatch;
+#define POINTER_TO_HEX_MAX_STRLEN 19 // 0x<...>
+    size_t srpl_connection_name_length = strlen(instance->instance_name) + 2 + POINTER_TO_HEX_MAX_STRLEN + 3;
+    char *new_name = malloc(srpl_connection_name_length);
+    if (new_name != NULL) {
+        free(connection->name);
+        connection->name = new_name;
+        // unidentified connection is by definition an incoming connection.
+        snprintf(new_name, srpl_connection_name_length, "<%s (%p)", instance->instance_name, connection);
+    }
+    srp_server_t *server_state = cur->domain->server_state;
+    for (sp = &server_state->unmatched_instances; *sp; sp = &(*sp)->next) {
         if (*sp == cur) {
             *sp = cur->next;
             break;
         }
     }
 
-    // Release the reference that the unmatched instance list had. This should dispose of it, since we didn't have a
-    // reference to this instance from the connection itself.
+    // Release the unmatched instance list's reference to the unmatched instance. We already released the srpl_connection's
+    // reference, so this should dispose of the unmatched instance.
     RELEASE_HERE(cur, srpl_instance);
 
     if (instance->domain == NULL || instance->domain->srpl_opstate != SRPL_OPSTATE_ROUTINE) {
@@ -2681,8 +2808,17 @@
         goto out;
     }
 
+    if (connection->database_synchronized) {
+        srpl_state_transition_by_dataset_id(instance->domain, instance);
+    }
+
     // Release any older connection we might have.
     if (instance->connection) {
+        srpl_connection_t *old_connection = instance->connection;
+        if (old_connection->instance != NULL) {
+            RELEASE_HERE(old_connection->instance, srpl_instance);
+            old_connection->instance = NULL;
+        }
         srpl_disconnect(instance->connection);
         RELEASE_HERE(instance->connection, srpl_connection); // Instance still holds reference.
         instance->connection = NULL;
@@ -2771,6 +2907,7 @@
                          srpl_connection->connected_address.sin.sin_port: srpl_connection->connected_address.sin6.sin6_port);
         return false;
     }
+    srpl_connection->is_server = false;
     ADDR_NAME_LOGGER(INFO, &srpl_connection->connected_address, "connecting to address ", " for instance ", " port ",
                      srpl_connection->name, srpl_connection->connected_address.sa.sa_family == AF_INET ?
                      srpl_connection->connected_address.sin.sin_port: srpl_connection->connected_address.sin6.sin6_port);
@@ -2779,15 +2916,17 @@
 }
 
 static void
-srpl_instance_is_me(srpl_instance_t *instance, srpl_instance_service_t *service, const char *ifname, const addr_t *address)
+srpl_instance_is_me(srpl_instance_t *instance, srpl_instance_service_t *service, const char *ifname, const addr_t *address, bool pid_match)
 {
     instance->is_me = true;
     if (ifname != NULL) {
         INFO(PUB_S_SRP "/" PUB_S_SRP ": name server for service " PRI_S_SRP " is me.", service->host_name, ifname, service->full_service_name);
     } else if (address != NULL) {
         ADDR_NAME_LOGGER(INFO, address, "", " service ", " is me. ", service->host_name, 0);
+    } else if (pid_match) {
+        INFO(PUB_S_SRP ": partner id %" PRIx64 "match.", instance->instance_name, instance->partner_id);
     } else {
-        ERROR("srpl_instance_is_me with null ifname and address!");
+        ERROR("null ifname and address, partner id doesn't match!");
         return;
     }
 
@@ -2799,7 +2938,7 @@
 }
 
 static bool
-srpl_my_address_check(const addr_t *address)
+srpl_my_address_check(srp_server_t *server_state, const addr_t *address)
 {
     static interface_address_state_t *ifaddrs = NULL;
     interface_address_state_t *ifa;
@@ -2808,7 +2947,7 @@
     const time_t now = srp_time();
     if (last_fetch == 0 || now - last_fetch > 60) {
         last_fetch = now;
-        ioloop_map_interface_addresses_here(&ifaddrs, NULL, NULL, NULL);
+        ioloop_map_interface_addresses_here(server_state, &ifaddrs, NULL, NULL, NULL);
     }
     // See if there's a match.
     for (ifa = ifaddrs; ifa; ifa = ifa->next) {
@@ -2820,7 +2959,7 @@
 }
 
 static void
-srpl_instance_address_callback(void *context, addr_t *address, bool added, int err)
+srpl_instance_address_callback(void *context, addr_t *address, bool added, bool more, int err)
 {
     srpl_instance_service_t *service = context;
     srpl_instance_t *instance = service->instance;
@@ -2836,7 +2975,8 @@
 
     if (added) {
         bool matched_unidentified = false;
-        srpl_instance_t **up = &unmatched_instances;
+        srp_server_t *server_state = service->domain->server_state;
+        srpl_instance_t **up = &server_state->unmatched_instances;
         while (*up != NULL) {
             srpl_instance_t *unmatched_instance = *up;
             srpl_connection_t *unidentified = unmatched_instance->connection;
@@ -2886,15 +3026,13 @@
             }
         }
 
-        if (srpl_my_address_check(address)) {
-            srpl_instance_is_me(instance, service, NULL, address);
-        }
-
-        // Generate an event indicating that we have a new address.
-        else if (!matched_unidentified && instance->connection != NULL && !instance->connection->is_server) {
-            srpl_event_t event;
-            srpl_event_initialize(&event, srpl_event_address_add);
-            srpl_event_deliver(instance->connection, &event);
+        if (srpl_my_address_check(server_state, address)) {
+            srpl_instance_is_me(instance, service, NULL, address, false);
+        } else {
+            instance->added_address = true;
+            if (matched_unidentified) {
+                instance->matched_unidentified = true;
+            }
         }
     } else {
         srpl_event_t event;
@@ -2907,6 +3045,23 @@
             }
         }
     }
+
+    INFO(PRI_S_SRP " on instance " PRI_S_SRP ", matched_unidentified " PRI_S_SRP ", added_address " PRI_S_SRP,
+         more ? "more coming" : "done", instance->instance_name, instance->matched_unidentified ? "yes" : "no",
+         instance->added_address ? "yes" : "no");
+    if (!more) {
+        if (instance->added_address && !instance->matched_unidentified) {
+            // Generate an event indicating that we have a new address.
+            if (instance->connection != NULL && !instance->connection->is_server) {
+                srpl_event_t event;
+                srpl_event_initialize(&event, srpl_event_address_add);
+                srpl_event_deliver(instance->connection, &event);
+            }
+        }
+        // reset added_address and matched_unidentified
+        instance->added_address = false;
+        instance->matched_unidentified = false;
+    }
 }
 
 static void
@@ -2916,20 +3071,13 @@
     for (instance = domain->instances; instance != NULL; instance = next) {
         next = instance->next;
         if (instance->have_dataset_id) {
-            int64_t distance = domain->dataset_id - instance->dataset_id;
-            if (distance > 0 || (distance == EQUI_DISTANCE64 &&
-                (int64_t)(domain->dataset_id) > (int64_t)(instance->dataset_id)))
-            {
+            if (srpl_dataset_id_compare(domain->dataset_id, instance->dataset_id) > 0) {
                 instance->sync_to_join = false;
                 if (instance->connection != NULL) {
                     INFO("abandon dataset with instance " PRI_S_SRP " of partner id %" PRIx64,
                          instance->instance_name, instance->partner_id);
                     srpl_connection_reset(instance->connection);
-                    if (instance->connection != NULL && instance->connection->connection != NULL) {
-                        srpl_trigger_disconnect(instance->connection);
-                    } else {
-                        srpl_instance_reconnect(instance);
-                    }
+                    srpl_connection_next_state(instance->connection, srpl_state_disconnected);
                 }
             }
         }
@@ -2938,105 +3086,58 @@
 
 static void srpl_transition_to_startup_state(srpl_domain_t *domain);
 
-static bool
-srpl_find_instance_with_current_dataset(srpl_domain_t *domain)
+static int
+srpl_dataset_id_compare(uint64_t id1, uint64_t id2)
 {
-    for (srpl_instance_t *instance = domain->instances; instance!= NULL; instance = instance->next) {
-        if (instance->dataset_id == domain->dataset_id) {
-            return true;
-        }
+    int64_t distance = id1 - id2;
+    if (distance == 0) {
+        return 0;
+    } else if (distance > 0) {
+        return 1;
+    } else if (distance == EQUI_DISTANCE64 && (int64_t)id1 > (int64_t)id2) {
+        // the number 2^(N−1) (where N is 64) is equidistant in both directions in sequence number terms.
+        // they are both considered to be "less than" each other. This is true for any serial number with
+        // distance of 0x8000000000000000 between them. To break the tie, higher signed number wins.
+        return 1;
+    } else {
+        return -1;
     }
-    return false;
 }
 
-static uint64_t
-srpl_instances_max_dataset_id(srpl_domain_t *domain)
+static bool
+srpl_instances_max_dataset_id(srpl_domain_t *domain, uint64_t *dataset_id)
 {
-    // at this point, domain->instances should contain at least one instance
-    srpl_instance_t *instance = domain->instances;
-    uint64_t max = instance->dataset_id;
-    instance = instance->next;
-    while (instance) {
-        int64_t distance = instance->dataset_id - max;
-        // the number 2^(N−1) (where N is 64) is equidistant in both directions in sequence number terms.
-        // they are both considered to be "less than" each other. This is true for any sequence number with
-        // distance of 0x8000000000000000 between them. To break the tie, higher signed number wins.
-        if (distance == EQUI_DISTANCE64) {
-            if ((int64_t)(instance->dataset_id) > (int64_t)max) {
+    srpl_instance_t *instance = NULL;
+    bool have_max = false;
+    uint64_t max = 0;
+
+    for (instance = domain->instances; instance != NULL; instance = instance->next) {
+        if (instance->sync_fail || instance->discontinuing || instance->version_mismatch) {
+            continue;
+        }
+        if (!have_max) {
+            max = instance->dataset_id;
+            have_max = true;
+        } else {
+            if (srpl_dataset_id_compare(instance->dataset_id, max) > 0) {
                 max = instance->dataset_id;
             }
-        } else if (distance > 0) {
-            max =  instance->dataset_id;
         }
-        instance = instance->next;
     }
-    return max;
-}
-
-// Return value
-// True: continue setting up the replication with the discovered partner.
-//       True is returned if the discovered dataset id is equal to or greater
-//       than the current dataset id held for this domain
-// False: Skip setting up the replication with the discovered partner.
-//       False is returned if the discovered dataset id is smaller than the
-//       current dataset id
-static bool
-srpl_evaluate_instance_dataset_id(srpl_domain_t *domain, srpl_instance_t *instance)
-{
-    uint64_t dataset_id = instance->dataset_id;
-    if (!domain->have_dataset_id) {
-        domain->dataset_id = dataset_id;
-        domain->have_dataset_id = true;
-        INFO("domain " PRI_S_SRP ": first dataset id %" PRIx64, domain->name, dataset_id);
-        return true;
+    if (!have_max) {
+        INFO("no available dataset id.");
     } else {
-        if (domain->srpl_opstate != SRPL_OPSTATE_ROUTINE &&
-            !srpl_find_instance_with_current_dataset(domain))
-        {
-            // the instances that have generated the dataset id are gone; in this case,
-            // we update the dataset id to the max of all the current instances
-            domain->dataset_id = srpl_instances_max_dataset_id(domain);
-            INFO("all instances advertising the current dataset id are gone; update the dataset id to %" PRIx64, domain->dataset_id);
-        }
-        int64_t distance = domain->dataset_id - dataset_id;
-        if (distance == 0) {
-            return true;
-        }
-        bool dataset_id_win = false;
-        if (distance > 0 || (distance == EQUI_DISTANCE64 &&
-            (int64_t)(domain->dataset_id) > (int64_t)dataset_id))
-        {
-            dataset_id_win = true;
-        }
-        if (!dataset_id_win) {
-            // local dataset_id is smaller than the remote partner's.
-            INFO("domain " PRI_S_SRP ": current dataset id %" PRIx64 " < discovered dataset id %" PRIx64
-                 ", abandon current dataset and re-enter the startup state",
-                 domain->name, domain->dataset_id, dataset_id);
-            domain->dataset_id = dataset_id;
-            // DNS-SD SRP Replication Spec: if at any time (regardless of "startup" or "routine
-            // operation" state) an SRPL partner discovers that it is synchronizing with a
-            // non-preferred dataset ID, it MUST abandon that dataset, re-enter the "startup"
-            // state, and attempt to synchronize with the (newly discovered) preferred dataset id.
-            srpl_abandon_nonpreferred_dataset(domain);
-            srpl_transition_to_startup_state(domain);
-            return true;
-        } else {
-            // local dataset_id is larger than the remote partner's
-            instance->sync_to_join = false;
-            INFO("domain " PRI_S_SRP ": current dataset id %" PRIx64 " > discovered dataset id %" PRIx64
-                 ", skip setting up replication with the remote partner",
-                 domain->name, domain->dataset_id, dataset_id);
-        }
+        *dataset_id = max;
     }
-    return false;
+    return have_max;
 }
 
 static void
 srpl_instance_add(const char *hostname, const char *service_name,
                   const char *ifname, srpl_domain_t *domain, srpl_instance_service_t *service,
                   bool have_partner_id, uint64_t advertised_partner_id,
-                  bool have_dataset_id, uint64_t advertised_dataset_id)
+                  bool have_dataset_id, uint64_t advertised_dataset_id,
+                  bool have_priority, uint32_t advertised_priority)
 {
     srpl_instance_t **sp, *instance;
     srpl_instance_service_t **hp;
@@ -3085,7 +3186,8 @@
             *sp = instance;
             INFO("create a new instance for service " PRI_S_SRP, service_name);
         } else {
-            for (hp = &instance->services; *hp != NULL; hp = &(*hp)->next);
+            for (hp = &instance->services; *hp != NULL; hp = &(*hp)->next)
+                ;
             *hp = service;
             INFO("instance " PRI_S_SRP " exists; just link the service " PRI_S_SRP, instance->instance_name, service_name);
         }
@@ -3129,16 +3231,17 @@
         INFO("instance " PRI_S_SRP " update partner_id to %" PRIx64, instance->instance_name, advertised_partner_id);
     }
 
-    if (!srpl_evaluate_instance_dataset_id(domain, instance)) {
-        INFO("non-preferred dataset id %" PRIx64 " for domain " PRI_S_SRP ", so we should not connect.",
-             advertised_dataset_id, domain->name);
-        return;
+    // If this add changed the priority, we may want to re-attempt a connect.
+    if (have_priority && (!instance->have_priority || instance->priority != advertised_priority)) {
+        some_id_updated = true;
+        instance->have_priority = true;
+        instance->priority = advertised_priority;
+        INFO("instance " PRI_S_SRP " update priority to %" PRIx32, instance->instance_name, advertised_priority);
     }
-
     // To join the replication, sync with remote partners that are discovered during the
     // discovery window.
     if (domain->partner_discovery_pending) {
-         instance->sync_to_join = true;
+         instance->discovered_in_window = true;
      }
 
     // If the hostname changed, we need to restart the address query.
@@ -3186,12 +3289,17 @@
         }
     }
 
+    if (instance->have_partner_id &&
+        domain->partner_id  == instance->partner_id)
+    {
+        srpl_instance_is_me(instance, service, NULL, NULL, true);
+    }
+
     // If there's no existing connection, the partner initiates an outgoing connection if
     // it is in the startup state or its partner id is greater than the remote partner id.
-    if (!instance->is_me &&
-        instance->connection == NULL &&
-        (some_id_updated ||
-         (domain->srpl_opstate == SRPL_OPSTATE_STARTUP || domain->partner_id > advertised_partner_id)))
+    if (!instance->is_me && ((instance->connection == NULL || some_id_updated) &&
+        (domain->srpl_opstate == SRPL_OPSTATE_STARTUP ||
+         domain->partner_id > advertised_partner_id)))
     {
         char msg_buf[256];
         if (domain->srpl_opstate == SRPL_OPSTATE_STARTUP) {
@@ -3205,9 +3313,24 @@
              instance->instance_name, instance->partner_id, msg_buf);
         if (instance->connection != NULL && instance->connection->connection != NULL) {
             srpl_trigger_disconnect(instance->connection);
+            srpl_connection_next_state(instance->connection, srpl_state_idle);
         } else {
             srpl_instance_reconnect(instance);
         }
+    } else {
+        INFO(PRI_S_SRP ": not making outgoing connection: " PUB_S_SRP "is_me, connection = %p, " PRI_S_SRP
+             "some_id_updated, local partner_id %" PRIx64 ", remote partner_id %" PRIx64,
+             instance->instance_name, instance->is_me ? "" : "!", instance->connection, some_id_updated ? "" : "!",
+             domain->partner_id, advertised_partner_id);
+        // it's not our job to connect, but since there's some id change, we'd disconnect
+        // the current connection and trigger the peer to reconnect.
+        if (some_id_updated && instance->connection != NULL &&
+            instance->connection->connection != NULL)
+        {
+            INFO("some id updated, disconnect the current connection.");
+            srpl_trigger_disconnect(instance->connection);
+            srpl_connection_next_state(instance->connection, srpl_state_idle);
+        }
     }
 }
 
@@ -3221,16 +3344,21 @@
     const char *partner_id_string;
     const char *dataset_id_string;
     const char *xpanid_string;
+    const char *priority_string;
     uint8_t partner_id_string_len;
     uint8_t dataset_id_string_len;
     uint8_t xpanid_string_len;
+    uint8_t priority_string_len;
     char partner_id_buf[INT64_HEX_STRING_MAX];
     char dataset_id_buf[INT64_HEX_STRING_MAX];
     char xpanid_buf[INT64_HEX_STRING_MAX];
+    char priority_buf[INT64_HEX_STRING_MAX];
     uint64_t advertised_partner_id = 0;
     bool have_partner_id = false;
     uint64_t advertised_dataset_id = 0;
     bool have_dataset_id = false;
+    bool have_priority = false;
+    uint16_t advertised_priority = 0;
     srpl_instance_service_t **sp;
     srp_server_t *server_state = domain->server_state;
 
@@ -3271,6 +3399,12 @@
         free(domain_terminated);
         return;
     }
+
+    if (server_state->current_thread_domain_name == NULL) {
+        FAULT("current_thread_domain_name is NULL.");
+        return;
+    }
+
     if (strcmp(domain->name, server_state->current_thread_domain_name)) {
         INFO("discovered srpl instance is not for current thread domain, so not setting up replication.");
         return;
@@ -3332,6 +3466,20 @@
         }
     }
 
+    priority_string = TXTRecordGetValuePtr(service->txt_length, service->txt_rdata, "priority", &priority_string_len);
+    if (priority_string != NULL && priority_string_len < INT64_HEX_STRING_MAX) {
+        char *endptr, *nulptr;
+        unsigned long long num;
+        memcpy(priority_buf, priority_string, priority_string_len);
+        nulptr = &priority_buf[priority_string_len];
+        *nulptr = '\0';
+        num = strtoull(priority_buf, &endptr, 16);
+        if (num < UINT64_MAX && endptr == nulptr) {
+            advertised_priority = num;
+            have_priority = true;
+        }
+    }
+
     dns_rr_t srv_record;
     unsigned offset = 0;
     memset(&srv_record, 0, sizeof(srv_record));
@@ -3352,7 +3500,8 @@
     dns_name_free(srv_record.data.srv.name);
 
     srpl_instance_add(namebuf, service->full_service_name, ifname, service->domain, service, have_partner_id,
-                      advertised_partner_id, have_dataset_id, advertised_dataset_id);
+                      advertised_partner_id, have_dataset_id, advertised_dataset_id, have_priority,
+                      advertised_priority);
 
     // After the service is associated with a resolved instance, we should take it off the unresolved
     // list if the service is still on it. If the service fails to assocaited with an instance because
@@ -3395,8 +3544,8 @@
             return;
         }
     }
-    ioloop_add_wake_event(service->resolve_wakeup, service,
-                          srpl_instance_service_newdata_timeout, srpl_instance_service_context_release, 1000); // max one second
+    ioloop_add_wake_event(service->resolve_wakeup, service, srpl_instance_service_newdata_timeout,
+                          srpl_instance_service_context_release, 1000); // max one second
     RETAIN_HERE(service, srpl_instance_service); // for the wakeup
 }
 
@@ -3504,6 +3653,13 @@
 }
 
 static void
+srpl_domain_context_release(void *context)
+{
+    srpl_domain_t *domain = context;
+    RELEASE_HERE(domain, srpl_domain);
+}
+
+static void
 srpl_browse_callback(DNSServiceRef UNUSED sdRef, DNSServiceFlags flags, uint32_t interfaceIndex,
                      DNSServiceErrorType errorCode, const char *serviceName, const char *regtype,
                      const char *replyDomain, void *context)
@@ -3520,8 +3676,10 @@
         // Get rid of all instances on the domain, because we aren't going to get remove events for them.
         // If we start a new browse and get add events while the connections are still up, this will
         // have no effect.
-        for (srpl_instance_t *instance = domain->instances; instance; instance = instance->next) {
+        srpl_instance_t *next_instance;
+        for (srpl_instance_t *instance = domain->instances; instance; instance = next_instance) {
             INFO("_srpl-tls._tcp instance " PRI_S_SRP " went away.", instance->instance_name);
+            next_instance = instance->next;
             srpl_instance_discontinue(instance);
         }
 
@@ -3538,7 +3696,8 @@
         }
         if (domain->server_state->srpl_browse_wakeup != NULL) {
             ioloop_add_wake_event(domain->server_state->srpl_browse_wakeup,
-                                  domain, srpl_browse_restart, NULL, 1000);
+                                  domain, srpl_browse_restart, srpl_domain_context_release, 1000);
+            RETAIN_HERE(domain, srpl_domain);
         }
         return;
     }
@@ -3571,24 +3730,25 @@
     }
     if (flags & kDNSServiceFlagsAdd) {
         if (service != NULL) {
+            // it's possible that a service goes away and starts discontinuing, and before the timeout,
+            // the service comes back again. In this case, since the service is still on the list, it
+            // appears as a duplicate add. But we should cancel the discontinue timer.
+            if (service->discontinue_timeout != NULL) {
+                if (service->discontinuing) {
+                    INFO("discontinue on service " PRI_S_SRP " canceled.", service->full_service_name);
+                    ioloop_cancel_wake_event(service->discontinue_timeout);
+                    service->discontinuing = false;
+                    if (service->instance != NULL) {
+                        service->instance->discontinuing = false;
+                    }
+                }
+            }
+
             if (service->resolve_started) {
                 INFO(PRI_S_SRP ": resolve_started true, incrementing num_copies to %d",
                      full_service_name, service->num_copies + 1);
                 service->num_copies++;
                 INFO("duplicate add for service " PRI_S_SRP, full_service_name);
-                // it's possbile that a service goes away and starts discontinuing, and before the timeout,
-                // the service comes back again. In this case, since the service is still on the list, it
-                // appears as a duplicate add. But we should cancel the discontinue timer.
-                if (service->discontinue_timeout != NULL) {
-                    if (service->discontinuing) {
-                        INFO("discontinue on service " PRI_S_SRP " canceled.", service->full_service_name);
-                        ioloop_cancel_wake_event(service->discontinue_timeout);
-                        service->discontinuing = false;
-                        if (service->instance != NULL) {
-                            service->instance->discontinuing = false;
-                        }
-                    }
-                }
                 return;
              }
              // In this case the service went away and came back, so service->resolve_started is false, but the
@@ -3648,19 +3808,6 @@
         }
         INFO("resolving " PRI_S_SRP, full_service_name);
         service->resolve_started = true;
-
-        // If we have a discontinue timer going, cancel it.
-        if (service->discontinue_timeout != NULL) {
-            if (service->discontinuing) {
-                INFO("discontinue on service " PRI_S_SRP " canceled.", service->full_service_name);
-                ioloop_cancel_wake_event(service->discontinue_timeout);
-                service->discontinuing = false;
-                if (service->instance != NULL) {
-                    service->instance->discontinuing = false;
-                }
-            }
-        }
-
     } else {
         if (service != NULL) {
             INFO(PRI_S_SRP ": decrementing num_copies to %d", full_service_name, service->num_copies - 1);
@@ -3679,13 +3826,6 @@
 }
 
 static void
-srpl_domain_context_release(void *context)
-{
-    srpl_domain_t *domain = context;
-    RELEASE_HERE(domain, srpl_domain);
-}
-
-static void
 srpl_dnssd_txn_fail(void *context, int err)
 {
     srpl_domain_t *domain = context;
@@ -3706,17 +3846,18 @@
         ERROR("Unable to query for NS records for " PRI_S_SRP, domain->name);
         return false;
     }
-    domain->query = ioloop_dnssd_txn_add(sdref, srpl_domain_context_release, NULL, srpl_dnssd_txn_fail);
+    domain->query = ioloop_dnssd_txn_add(sdref, domain, srpl_domain_context_release, srpl_dnssd_txn_fail);
     if (domain->query == NULL) {
         ERROR("Unable to set up ioloop transaction for NS query on " PRI_S_SRP, domain->name);
         DNSServiceRefDeallocate(sdref);
         return false;
     }
+    RETAIN_HERE(domain, srpl_domain); // For the browse
     return true;
 }
 
-static void
-srpl_domain_add(srp_server_t *server_state, const char *domain_name)
+srpl_domain_t *
+srpl_domain_create_or_copy(srp_server_t *server_state, const char *domain_name)
 {
     srpl_domain_t **dp, *domain;
 
@@ -3734,7 +3875,7 @@
         if (domain == NULL || (domain->name = strdup(domain_name)) == NULL) {
             ERROR("Unable to allocate replication structure for domain " PRI_S_SRP, domain_name);
             free(domain);
-            return;
+            return NULL;
         }
         *dp = domain;
         // Hold a reference for the domain list
@@ -3742,29 +3883,40 @@
         INFO("New service replication browsing domain: " PRI_S_SRP, domain->name);
 
         domain->srpl_opstate = SRPL_OPSTATE_STARTUP;
-        domain->partner_discovery_timeout = ioloop_wakeup_create();
-        if (domain->partner_discovery_timeout) {
-            ioloop_add_wake_event(domain->partner_discovery_timeout, domain,
-                                  srpl_partner_discovery_timeout, NULL,
-                                  MIN_PARTNER_DISCOVERY_INTERVAL + srp_random16() % PARTNER_DISCOVERY_INTERVAL_RANGE);
-        } else {
-            ERROR("unable to add wakeup event for partner discovery for domain " PRI_S_SRP, domain->name);
-            return;
-        }
-        domain->partner_discovery_pending = true;
+        domain->server_state = server_state;
         domain->partner_id = srp_random64();
         INFO("generate partner id %" PRIx64 " for domain " PRI_S_SRP, domain->partner_id, domain->name);
     } else {
         ERROR("Unexpected duplicate replication domain: " PRI_S_SRP, domain_name);
+        return NULL;
+    }
+    return domain;
+}
+
+static void
+srpl_domain_add(srp_server_t *server_state, const char *domain_name)
+{
+    srpl_domain_t *domain = srpl_domain_create_or_copy(server_state, domain_name);
+    if (domain == NULL) {
         return;
     }
 
+    domain->partner_discovery_timeout = ioloop_wakeup_create();
+    if (domain->partner_discovery_timeout) {
+        ioloop_add_wake_event(domain->partner_discovery_timeout, domain,
+                              srpl_partner_discovery_timeout, srpl_domain_context_release,
+                              MIN_PARTNER_DISCOVERY_INTERVAL + srp_random16() % PARTNER_DISCOVERY_INTERVAL_RANGE);
+        RETAIN_HERE(domain, srpl_domain);
+    } else {
+        ERROR("unable to add wakeup event for partner discovery for domain " PRI_S_SRP, domain->name);
+        return;
+    }
+    domain->partner_discovery_pending = true;
+
     // Start a browse on the domain.
     if (!srpl_domain_browse_start(domain)) {
         return;
     }
-    domain->server_state = server_state;
-    RETAIN_HERE(domain, srpl_domain);
 }
 
 static void
@@ -3998,6 +4150,51 @@
     srpl_connection_discontinue(srpl_connection);
 }
 
+static void
+srpl_connection_state_timeout(void *context)
+{
+    srpl_connection_t *srpl_connection = context;
+    srpl_instance_t *instance = srpl_connection->instance;
+    srpl_domain_t *domain = srpl_connection_domain(srpl_connection);
+
+    // Connection might have been discontinued before we came back.
+    if (instance == NULL) {
+        return;
+    }
+
+    // If the srpl connection has been in the current state for timeout and not received any
+    // event to get out of the current state, we assume the peer is gone or unavailable and
+    // can exclude this instance when we make a decision to enter the routine state.
+    instance->sync_fail = true;
+    INFO("connection for instance " PRI_S_SRP " timed out in state " PUB_S_SRP,
+         instance->instance_name, srpl_connection->state_name);
+    if (instance != NULL && instance->sync_to_join &&
+        domain != NULL && domain->srpl_opstate != SRPL_OPSTATE_ROUTINE)
+    {
+        instance->sync_to_join = false;
+        srpl_maybe_sync_or_transition(domain);
+    }
+}
+
+static void
+srpl_connection_schedule_state_timeout(srpl_connection_t *srpl_connection, uint32_t when)
+{
+    // Create a state timer on the srpl_connection_t
+    if (srpl_connection->state_timeout == NULL) {
+        srpl_connection->state_timeout = ioloop_wakeup_create();
+        if (srpl_connection->state_timeout == NULL) {
+            ERROR("no memory for state_timeout for service instance " PRI_S_SRP, srpl_connection->name);
+            return;
+        }
+    } else {
+        ioloop_cancel_wake_event(srpl_connection->state_timeout);
+    }
+    ioloop_add_wake_event(srpl_connection->state_timeout, srpl_connection, srpl_connection_state_timeout,
+                          srpl_connection_context_release, when);
+    RETAIN_HERE(srpl_connection, srpl_connection); // the timer has a reference.
+    return;
+}
+
 // We arrive at the disconnected state when there is no connection to make, or no need to make a connection.
 // This state takes no action, but waits for events. If we get an add event and we don't have a viable incoming
 // connection, we go to the next_address_get event.
@@ -4007,8 +4204,10 @@
     STATE_ANNOUNCE(srpl_connection, event);
 
     if (event == NULL) {
+        srpl_connection_schedule_state_timeout(srpl_connection, SRPL_STATE_TIMEOUT);
         return srpl_state_invalid;
     } else if (event->event_type == srpl_event_address_add) {
+        ioloop_cancel_wake_event(srpl_connection->state_timeout);
         return srpl_state_next_address_get;
     } else {
         UNEXPECTED_EVENT(srpl_connection, event);
@@ -4105,10 +4304,12 @@
     REQUIRE_SRPL_INSTANCE(srpl_connection);
     STATE_ANNOUNCE_NO_EVENTS(srpl_connection);
     if (event == NULL) {
-        return srpl_state_invalid; // Wait for events
+        srpl_connection_schedule_state_timeout(srpl_connection, SRPL_STATE_TIMEOUT);
+        return srpl_state_invalid;
     } else if (event->event_type == srpl_event_server_disconnect ||
                event->event_type == srpl_event_reconnect_timer_expiry)
     {
+        ioloop_cancel_wake_event(srpl_connection->state_timeout);
         INFO(PRI_S_SRP ": event " PUB_S_SRP " received in state " PUB_S_SRP,
              srpl_connection->name, event->name, srpl_connection->state_name);
         return srpl_state_next_address_get;
@@ -4118,6 +4319,27 @@
     }
 }
 
+static void
+srpl_maybe_propose_new_dataset_id(srpl_domain_t *domain)
+{
+    if (domain->have_dataset_id) {
+        for(srpl_instance_t *instance = domain->instances; instance != NULL; instance = instance->next)
+        {
+            // as long as there's one instance of the proposed dataset id that has not failed
+            // to sync, we are going to wait
+            if (!instance->sync_fail &&
+                instance->dataset_id == domain->dataset_id)
+            {
+                INFO("instance " PRI_S_SRP " has matched dataset_id %" PRIx64,
+                     instance->instance_name, instance->dataset_id);
+                return;
+            }
+        }
+    }
+    domain->have_dataset_id = srpl_instances_max_dataset_id(domain, &domain->dataset_id);
+    INFO(PRI_S_SRP "propose a new dataset id %" PRIx64, domain->have_dataset_id? "": "fail to ", domain->dataset_id);
+}
+
 // We've received a timeout event on the reconnect timer. Generate a reconnect_timeout event and send it to the
 // connection.
 static void
@@ -4127,13 +4349,15 @@
     srpl_instance_t *instance = srpl_connection->instance;
     srpl_domain_t *domain = srpl_connection_domain(srpl_connection);
 
+    INFO("reconnect timeout on " PRI_S_SRP, srpl_connection->name);
     srpl_event_t event;
     srpl_event_initialize(&event, srpl_event_reconnect_timer_expiry);
     srpl_event_deliver(srpl_connection, &event);
-    INFO("reconnect timeout on " PRI_S_SRP, srpl_connection->name);
     // If we have tried to connect to all the addresses but failed, we assume the peer is
     // gone. We no longer need to synchronize with this peer and if this was an obstacle
     // to enter the routine state, we should recheck again.
+    instance->sync_fail = true;
+    INFO("fail to sync with instance " PRI_S_SRP, instance->instance_name);
     if (instance != NULL && instance->sync_to_join &&
         domain != NULL && domain->srpl_opstate != SRPL_OPSTATE_ROUTINE)
     {
@@ -4169,14 +4393,27 @@
 {
     REQUIRE_SRPL_INSTANCE(srpl_connection);
     STATE_ANNOUNCE(srpl_connection, event);
+    srpl_domain_t *domain = srpl_connection_domain(srpl_connection);
 
     if (event == NULL) {
         return srpl_connection_schedule_reconnect_event(srpl_connection, 60 * 1000);
     }
     if (event->event_type == srpl_event_reconnect_timer_expiry) {
+        // if it's not our job to reconnect, we move into idle.
+        if (domain->srpl_opstate != SRPL_OPSTATE_STARTUP &&
+            domain->partner_id < srpl_connection->instance->partner_id)
+        {
+            return srpl_state_idle;
+        }
         return srpl_state_next_address_get;
     } else if (event->event_type == srpl_event_address_add) {
         ioloop_cancel_wake_event(srpl_connection->reconnect_wakeup);
+        // if it's not our job to reconnect, we move into idle.
+        if (domain->srpl_opstate != SRPL_OPSTATE_STARTUP &&
+            domain->partner_id < srpl_connection->instance->partner_id)
+        {
+            return srpl_state_idle;
+        }
         return srpl_state_next_address_get;
     }
     UNEXPECTED_EVENT(srpl_connection, event);
@@ -4238,13 +4475,16 @@
 {
     STATE_ANNOUNCE(srpl_connection, event);
     if (event == NULL) {
+        srpl_connection_schedule_state_timeout(srpl_connection, SRPL_STATE_TIMEOUT);
         return srpl_state_invalid;
     } else if (event->event_type == srpl_event_disconnected) {
+        ioloop_cancel_wake_event(srpl_connection->state_timeout);
         // We tried to connect and the connection failed. This may mean that the information we see in the _srpl-tls.tcp
         // advertisement is wrong, or that the address records are wrong. Reconfirm the records.
         srpl_reconfirm(srpl_connection);
         return srpl_state_next_address_get;
     } else if (event->event_type == srpl_event_connected) {
+        ioloop_cancel_wake_event(srpl_connection->state_timeout);
         return srpl_state_session_send;
     } else {
         UNEXPECTED_EVENT_NO_ERROR(srpl_connection, event);
@@ -4252,11 +4492,31 @@
     return srpl_state_invalid;
 }
 
+static void
+srpl_sync_wait_check(void *context)
+{
+    srpl_connection_t *srpl_connection = context;
+
+    if (srpl_connection->instance == NULL) {
+        FAULT("srpl_connection->instance shouldn't ever be NULL here, but it is.");
+        return;
+    }
+    if (srpl_connection->instance->domain == NULL) {
+        FAULT("srpl_connection->instance->domain shouldn't ever be NULL here, but it is.");
+        return;
+    }
+    if (!srpl_connection->instance->domain->partner_discovery_pending) {
+        srpl_maybe_sync_or_transition(srpl_connection->instance->domain);
+    }
+}
+
 static srpl_state_t
 srpl_sync_wait_action(srpl_connection_t *srpl_connection, srpl_event_t *event)
 {
     STATE_ANNOUNCE(srpl_connection, event);
     if (event == NULL) {
+        // This will trigger a do_sync event if we should synchronize at this point.
+        ioloop_run_async(srpl_sync_wait_check, srpl_connection);
         return srpl_state_invalid;
     } else if (event->event_type == srpl_event_do_sync) {
         // When starting to sync, we reset the keepalive_interval so that we can detect
@@ -4296,6 +4556,19 @@
     if (event == NULL) {
         return srpl_state_invalid;
     } else if (event->event_type == srpl_event_session_response_received) {
+        // we are connecting and have sent the peer a session message. if the received session
+        // response shows that the peer has a lower version number, we don't have to sync with
+        // this peer to join the replication. we also note version_mismatch on the corresponding
+        // instance and move to the idle state. when we move to the routine state later, we'll
+        // pick a dataset id so that the sequence number of our anycast service would win over
+        // the low-version peers.
+        if (event->content.session.remote_version < SRPL_CURRENT_VERSION) { // version mismatch
+            INFO("instance " PRI_S_SRP " has a lower version number", srpl_connection->instance->instance_name);
+            srpl_connection->instance->version_mismatch = true;
+            srpl_connection->instance->sync_to_join = false;
+            return srpl_state_idle;
+        }
+
         srpl_connection->remote_partner_id = event->content.session.partner_id;
         srpl_connection->new_partner = event->content.session.new_partner;
         srpl_domain_t *domain = srpl_connection_domain(srpl_connection);
@@ -4395,7 +4668,7 @@
 }
 
 static bool
-srpl_can_transition_to_routine_state(const srpl_domain_t *domain)
+srpl_can_transition_to_routine_state(srpl_domain_t *domain)
 {
     if (domain == NULL) {
         INFO("returning false because there's no domain");
@@ -4415,32 +4688,66 @@
         return false;
     }
 
-    for (srpl_instance_t *instance = domain->instances; instance != NULL; instance = instance->next) {
-        // We skip checking the instance if the instance
-        // 1. is to myself (this could happen if I restarts and receives a stale advertisement from myself).
-        // 2. is not discovered during the discovery_timeout, or
-        // 3. is discontinuing
-        if (instance->is_me || !instance->sync_to_join || instance->discontinuing) {
-            INFO("instance " PUB_S_SRP ": is_me (" PUB_S_SRP ") or sync_to_join (" PUB_S_SRP ") or discontinuing (" PUB_S_SRP ")",
-                 instance->instance_name, instance->is_me ? "true" : "false", instance->sync_to_join ? "true" : "false",
-                 instance->discontinuing ? "true" : "false");
-            continue;
-        }
-        // Instance is a valid partner that we should sync with to possibly move to the routine state
-        if (instance->connection == NULL ||
-            !instance->connection->database_synchronized)
-        {
-            INFO("synchronization on " PRI_S_SRP " with partner_id %" PRIx64 " is not ready (%p " PUB_S_SRP ").",
-                 instance->instance_name, instance->partner_id, instance->connection,
-                 (instance->connection == NULL ? "null" :
-                 instance->connection->database_synchronized ? "true" : "false"));
-            return false;
+    if (domain->have_dataset_id) {
+        for (srpl_instance_t *instance = domain->instances; instance != NULL; instance = instance->next) {
+            // We skip checking the instance if the instance
+            // 1. is to myself (this could happen if I restarts and receives a stale advertisement from myself).
+            // 2. is not discovered during the discovery_timeout, or
+            // 3. the dataset id is not what we are looking for, or
+            // 4. is discontinuing, or
+            // 5. has lower version
+            if (instance->is_me || !instance->sync_to_join ||
+                instance->dataset_id != domain->dataset_id ||
+                instance->discontinuing || instance->version_mismatch)
+            {
+                INFO("instance " PUB_S_SRP ": is_me (" PUB_S_SRP ") or sync_to_join (" PUB_S_SRP
+                     ") or discontinuing (" PUB_S_SRP ") or version_mismatch (" PUB_S_SRP ")",
+                     instance->instance_name, instance->is_me ? "true" : "false", instance->sync_to_join ? "true" : "false",
+                     instance->discontinuing ? "true" : "false", instance->version_mismatch ? "true" : "false");
+                continue;
+            }
+            // Instance is a valid partner that we should sync with to possibly move to the routine state
+            if (instance->connection == NULL ||
+                !instance->connection->database_synchronized)
+            {
+                INFO("synchronization on " PRI_S_SRP " with partner_id %" PRIx64 " is not ready (%p " PUB_S_SRP ").",
+                     instance->instance_name, instance->partner_id, instance->connection,
+                     (instance->connection == NULL ? "null" :
+                     instance->connection->database_synchronized ? "true" : "false"));
+                return false;
+            }
         }
     }
     INFO("ready");
     return true;
 }
 
+static void
+srpl_store_dataset_id(srpl_domain_t *domain)
+{
+    uint64_t dataset_id = domain->dataset_id;
+    uint8_t msb;
+    OSStatus err;
+
+    // read out the stored msb of the dataset id. increment the msb and generate the dataset id.
+    const CFStringRef app_id = CFSTR("com.apple.srp-mdns-proxy.preferences");
+    const CFStringRef key = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("dataset-id-msb-%s"), domain->name);
+
+    if (key) {
+        msb = (dataset_id & 0xFF00000000000000) >> 56;
+        err = CFPrefs_SetInt64(app_id, key, msb);
+
+        if (err) {
+            ERROR("Unable to store the msb of the dataset id in preferences.");
+        }
+        INFO("store msb %d.", msb);
+        CFRelease(key);
+    } else {
+        ERROR("unable to create key for domain " PRI_S_SRP, domain->name);
+    }
+}
+
+
 // SRPL partners MUST persist the highest (most significant byte or MSB) of the dataset ID.
 // When generating a new dataset ID, the partner MUST increment the MSB of last used dataset
 // ID to use as MSB of new dataset ID and populate the lower 56 bits randomly. If there is no
@@ -4459,6 +4766,7 @@
     if (key) {
         msb = (uint8_t)CFPrefs_GetInt64(app_id, key, &err);
         if (err) {
+            INFO("fail to fetch msb, generate random dataset id.");
             dataset_id = srp_random64();
         } else {
             dataset_id = (((uint64_t)msb+1) << 56) | (srp_random64() & LOWER56_BIT_MASK);
@@ -4494,8 +4802,24 @@
     if (!domain->have_dataset_id) {
         domain->dataset_id = srpl_generate_store_dataset_id(domain);
         domain->have_dataset_id = true;
+        domain->dataset_id_committed = true;
         INFO("generate new dataset id %" PRIx64 " for domain " PRI_S_SRP,
              domain->dataset_id, domain->name);
+    } else {
+        // if there are instances that have lower version number but the MSB of their dataset_id
+        // is larger than ours, we'll increment the MSB of the instance dataset id and use it
+        // as our dataset id. this is to guarantee that the current version has a higher sequence
+        // number so that its service will be preferred.
+        for (srpl_instance_t *instance = domain->instances; instance != NULL; instance = instance->next) {
+            if (instance->version_mismatch &&
+                srpl_dataset_id_compare(instance->dataset_id & 0xFF00000000000000,
+                                        domain->dataset_id & 0xFF00000000000000) >= 0)
+            {
+                INFO("increment the MSB of the dataset_id %" PRIx64 " of instance " PRI_S_SRP,
+                     instance->dataset_id, instance->instance_name);
+                domain->dataset_id = instance->dataset_id + 0x0100000000000000;
+            }
+        }
     }
 #if STUB_ROUTER
     srp_server_t *server_state = domain->server_state;
@@ -4513,6 +4837,51 @@
 #endif
 }
 
+static bool
+srpl_keep_current_dataset_id(srpl_domain_t *domain, srpl_instance_t *instance)
+{
+    bool keep = true;
+    bool stored = domain->dataset_id_committed;
+    if (instance->have_dataset_id) {
+        uint64_t new = instance->dataset_id;
+        int compare = srpl_dataset_id_compare(new, domain->dataset_id);
+        if (compare == 0) {
+            domain->dataset_id_committed = true;
+            keep = true;
+            INFO("keep and commit dataset_id %" PRIx64, domain->dataset_id);
+        } else if (compare > 0) {
+            INFO("abandon dataset_id %" PRIx64 " and commit preferred %" PRIx64, domain->dataset_id, new);
+            domain->dataset_id = new;
+            domain->dataset_id_committed = true;
+            keep = false;
+        } else {
+            INFO("non-preferred dataset id %" PRIx64, instance->dataset_id);
+        }
+    }
+    // we store the msb of dataset id if we haven't done so for current
+    // committed dataset id or the committed dataset id has changed.
+    if ((!stored || !keep) && domain->dataset_id_committed) {
+        srpl_store_dataset_id(domain);
+    }
+    return keep;
+}
+
+static void
+srpl_state_transition_by_dataset_id(srpl_domain_t *domain, srpl_instance_t *instance)
+{
+    if (!srpl_keep_current_dataset_id(domain, instance)) {
+        // DNS-SD SRP Replication Spec: if at any time (regardless of "startup" or "routine
+        // operation" state) an SRPL partner discovers that it is synchronizing with a
+        // non-preferred dataset ID, it MUST abandon that dataset, re-enter the "startup"
+        // state, and attempt to synchronize with the (newly discovered) preferred dataset id.
+        INFO("more preferred dataset id %" PRIx64 ", reenter startup state", domain->dataset_id);
+        srpl_abandon_nonpreferred_dataset(domain);
+        srpl_transition_to_startup_state(domain);
+    } else {
+        srpl_maybe_sync_or_transition(domain);
+    }
+}
+
 // Used by srpl_send_candidates_wait_action and srpl_host_wait_action
 static srpl_state_t
 srpl_send_candidates_wait_event_process(srpl_connection_t *srpl_connection, srpl_event_t *event)
@@ -4521,12 +4890,9 @@
     if (event->event_type == srpl_event_send_candidates_response_received) {
         if (srpl_connection->is_server) {
             srpl_connection->database_synchronized = true;
-            if (domain->srpl_opstate != SRPL_OPSTATE_ROUTINE &&
-                srpl_connection->instance != NULL &&
-                srpl_connection->instance->sync_to_join)
-            {
-                srpl_maybe_sync_or_transition(domain);
-            }
+            srpl_instance_t *instance = srpl_connection->instance;
+            instance->sync_fail = false;
+            srpl_state_transition_by_dataset_id(domain, instance);
             return srpl_state_ready;
         } else {
             return srpl_state_send_candidates_message_wait;
@@ -4756,10 +5122,14 @@
         // for events. In this case, there's no real problem, and the successful update should trigger an update to be
         // sent to the remote.
         srpl_host_response_send(srpl_connection, dns_rcode_noerror);
+        INFO("candidate_no: freeing parts");
+        srpl_host_update_parts_free(&srpl_connection->stashed_host);
         ret = srpl_state_send_candidates_wait;
         break;
     case srpl_candidate_conflict:
         srpl_host_response_send(srpl_connection, dns_rcode_yxdomain);
+        INFO("candidate_conflict: freeing parts");
+        srpl_host_update_parts_free(&srpl_connection->stashed_host);
         ret = srpl_state_send_candidates_wait;
         break;
     }
@@ -4770,11 +5140,12 @@
 srpl_connection_host_apply(srpl_connection_t *srpl_connection)
 {
     DNS_NAME_GEN_SRP(srpl_connection->stashed_host.hostname, name_buf);
-    INFO("applying update from " PRI_S_SRP " for host " PRI_DNS_NAME_SRP " message #%d",
+    INFO("applying update from " PRI_S_SRP " for host " PRI_DNS_NAME_SRP ", %d messages",
          srpl_connection->name, DNS_NAME_PARAM_SRP(srpl_connection->stashed_host.hostname, name_buf),
-         srpl_connection->stashed_host.messages_processed);
-    if (!srp_dns_evaluate(NULL, srpl_connection->instance->domain->server_state, srpl_connection,
-                          srpl_connection->stashed_host.messages[srpl_connection->stashed_host.messages_processed]))
+         srpl_connection->stashed_host.num_messages);
+    if (!srp_parse_host_messages_evaluate(srpl_connection->instance->domain->server_state, srpl_connection,
+                                          srpl_connection->stashed_host.messages,
+                                          srpl_connection->stashed_host.num_messages))
     {
         srpl_host_response_send(srpl_connection, dns_rcode_formerr);
         return false;
@@ -4814,7 +5185,6 @@
 {
     srpl_event_t *event = context;
     srp_server_t *server_state = event->content.advertise_finished.server_state;
-
     for (srpl_domain_t *domain = server_state->srpl_domains; domain != NULL; domain = domain->next) {
         for (srpl_instance_t *instance = domain->instances; instance != NULL; instance = instance->next) {
             if (instance->connection != NULL) {
@@ -4822,7 +5192,7 @@
             }
         }
     }
-    for (srpl_instance_t *instance = unmatched_instances; instance != NULL; instance = instance->next) {
+    for (srpl_instance_t *instance = server_state->unmatched_instances; instance != NULL; instance = instance->next) {
         if (instance->connection != NULL) {
             srpl_event_deliver(instance->connection, event);
         }
@@ -4870,12 +5240,6 @@
     if (event == NULL) {
         return srpl_state_invalid; // Wait for events.
     } else if (event->event_type == srpl_event_advertise_finished) {
-        if (srpl_connection->stashed_host.rcode == dns_rcode_noerror) {
-            srpl_connection->stashed_host.messages_processed++;
-            if (srpl_connection->stashed_host.messages_processed < srpl_connection->stashed_host.num_messages) {
-                return srpl_state_candidate_host_apply;
-            }
-        }
         srpl_host_response_send(srpl_connection, event->content.advertise_finished.rcode);
         INFO("freeing parts");
         srpl_host_update_parts_free(&srpl_connection->stashed_host);
@@ -5043,12 +5407,9 @@
     } else {
         srpl_domain_t *domain = srpl_connection_domain(srpl_connection);
         srpl_connection->database_synchronized = true;
-        if (domain->srpl_opstate != SRPL_OPSTATE_ROUTINE &&
-            srpl_connection->instance != NULL &&
-            srpl_connection->instance->sync_to_join)
-        {
-            srpl_maybe_sync_or_transition(domain);
-        }
+        srpl_instance_t *instance = srpl_connection->instance;
+        instance->sync_fail = false;
+        srpl_state_transition_by_dataset_id(domain, instance);
         return srpl_state_ready;
     }
 }
@@ -5237,12 +5598,6 @@
         FAULT(PRI_S_SRP ": stashed host present, no messages.", srpl_connection->name);
         return srpl_state_ready;
     }
-    if (srpl_connection->stashed_host.rcode == dns_rcode_noerror) {
-        srpl_connection->stashed_host.messages_processed++;
-        if (srpl_connection->stashed_host.messages_processed < srpl_connection->stashed_host.num_messages) {
-            return srpl_state_stashed_host_apply;
-        }
-    }
     srpl_host_response_send(srpl_connection, srpl_connection->stashed_host.rcode);
     INFO("freeing parts");
     srpl_host_update_parts_free(&srpl_connection->stashed_host);
@@ -5259,6 +5614,14 @@
     if (event == NULL) {
         return srpl_state_invalid; // Wait for events.
     } else if (event->event_type == srpl_event_session_message_received) {
+        // we check the remote_version. if it's smaller than our current version, we note
+        // version_mismatch on the instance and move to the idle state. Since no response
+        // is sent out, the remote peer will stay in the session_response_wait.
+        if (event->content.session.remote_version < SRPL_CURRENT_VERSION) { // version mismatch
+            INFO("instance " PRI_S_SRP " has a lower version number", srpl_connection->instance->instance_name);
+            srpl_connection->instance->version_mismatch = true;
+            return srpl_state_idle;
+        }
         srpl_connection->remote_partner_id = event->content.session.partner_id;
         srpl_connection->new_partner = event->content.session.new_partner;
         return srpl_state_session_evaluate;
@@ -5297,6 +5660,18 @@
     }
 }
 
+#ifdef SRP_TEST_SERVER
+// When testing, we may want an srpl_connection_t that just calls back to the test system when an event
+// is delivered and otherwise does nothing.
+static srpl_state_t
+srpl_test_event_intercept_action(srpl_connection_t *srpl_connection, srpl_event_t *event)
+{
+    STATE_ANNOUNCE(srpl_connection, event);
+
+    return test_packet_srpl_intercept(srpl_connection, event);
+}
+#endif
+
 // Check to see if host is on the list of remaining candidates to send. If so, no need to do anything--it'll go out soon.
 static bool
 srpl_reschedule_candidate(srpl_connection_t *srpl_connection, adv_host_t *host)
@@ -5383,7 +5758,7 @@
             }
         }
     }
-    for (srpl_instance_t *instance = unmatched_instances; instance != NULL; instance = instance->next) {
+    for (srpl_instance_t *instance = server_state->unmatched_instances; instance != NULL; instance = instance->next) {
         if (instance->connection != NULL) {
             srpl_srp_client_update_send_event_to_connection(instance->connection, event);
         }
@@ -5495,6 +5870,10 @@
     { STATE_NAME_DECL(session_response_send),                srpl_session_response_send },
     // Wait for a "send candidates" message.
     { STATE_NAME_DECL(send_candidates_message_wait),         srpl_send_candidates_message_wait_action },
+
+#ifdef SRP_TEST_SERVER
+    { STATE_NAME_DECL(test_event_intercept),                 srpl_test_event_intercept_action },
+#endif
 };
 #define SRPL_NUM_CONNECTION_STATES (sizeof(srpl_connection_states) / sizeof(srpl_connection_state_t))
 
@@ -5519,7 +5898,7 @@
     return &srpl_connection_states[state];
 }
 
-static void
+void
 srpl_connection_next_state(srpl_connection_t *srpl_connection, srpl_state_t state)
 {
     srpl_state_t next_state = state;
@@ -5534,6 +5913,7 @@
         }
         srpl_connection->state = next_state;
         srpl_connection->state_name = new_state->name;
+        srpl_connection->state_start_time = srp_time();
         srpl_action_t action = new_state->action;
         if (action != NULL) {
             next_state = action(srpl_connection, NULL);
@@ -5606,6 +5986,69 @@
     return "unknown state";
 }
 
+void
+srpl_dump_connection_states(srp_server_t *server_state)
+{
+    srpl_domain_t *domain;
+    srpl_instance_t *instance;
+    srpl_connection_t *connection;
+    uint32_t days, hours, minutes, seconds;
+
+    for (domain = server_state->srpl_domains; domain != NULL; domain = domain->next) {
+        INFO("srpl connections in domain " PRI_S_SRP ":", domain->name);
+        for (instance = domain->instances; instance != NULL; instance = instance->next) {
+            connection = instance->connection;
+            if (connection != NULL) {
+                seconds = (uint32_t)(srp_time() - connection->state_start_time);
+                days = seconds / SECONDS_IN_DAY;
+                seconds -= days * SECONDS_IN_DAY;
+                hours = seconds / SECONDS_IN_HOUR;
+                seconds -= hours * SECONDS_IN_HOUR;
+                minutes = seconds / SECONDS_IN_MINUTE;
+                seconds -= minutes * SECONDS_IN_MINUTE;
+                INFO(PUB_S_SRP "connected to srpl instance " PRI_S_SRP " - connection " PRI_S_SRP
+                     ", in state " PUB_S_SRP " for %" PRIu32 " days %" PRIu32 " hours %" PRIu32 " minutes %" PRIu32 " seconds",
+                     connection->state > srpl_state_connecting ? "" : "not yet ",
+                     instance->instance_name, connection->name, srpl_state_name(connection->state),
+                     days, hours, minutes, seconds);
+            } else {
+                INFO("no connection to srpl instance " PRI_S_SRP PUB_S_SRP, instance->instance_name,
+                     instance->is_me ? ", is_me" : "");
+            }
+        }
+    }
+
+    for (instance = server_state->unmatched_instances; instance != NULL; instance = instance->next) {
+        connection = instance->connection;
+        if (connection != NULL) {
+            seconds = (uint32_t)(srp_time() - connection->state_start_time);
+            days = seconds / SECONDS_IN_DAY;
+            seconds -= days * SECONDS_IN_DAY;
+            hours = seconds / SECONDS_IN_HOUR;
+            seconds -= hours * SECONDS_IN_HOUR;
+            minutes = seconds / SECONDS_IN_MINUTE;
+            seconds -= minutes * SECONDS_IN_MINUTE;
+            INFO(PUB_S_SRP "connected to unidentified instance " PRI_S_SRP " - connection " PRI_S_SRP
+                 ", in state " PUB_S_SRP " for %" PRIu32 " days %" PRIu32 " hours %" PRIu32 " minutes %" PRIu32 " seconds",
+                 connection->state > srpl_state_connecting ? "" : "not yet ",
+                 instance->instance_name, connection->name, srpl_state_name(connection->state),
+                 days, hours, minutes, seconds);
+        }
+    }
+}
+
+void
+srpl_change_server_priority(srp_server_t *server_state, uint32_t new)
+{
+    if (server_state != NULL && new != server_state->priority) {
+        server_state->priority = new;
+        for (srpl_domain_t *domain = server_state->srpl_domains; domain != NULL; domain = domain->next) {
+            // priority changed. re-advertise.
+            srpl_domain_advertise(domain);
+        }
+    }
+}
+
 static void
 srpl_event_initialize(srpl_event_t *event, srpl_event_type_t event_type)
 {
@@ -5661,7 +6104,8 @@
         }
         if (srpl_domain->srpl_register_wakeup != NULL) {
             // Try registering again in one second.
-            ioloop_add_wake_event(srpl_domain->srpl_register_wakeup, srpl_domain, srpl_re_register, NULL, 1000);
+            ioloop_add_wake_event(srpl_domain->srpl_register_wakeup, srpl_domain, srpl_re_register, srpl_domain_context_release, 1000);
+            RETAIN_HERE(srpl_domain, srpl_domain);
         }
         return;
     }
@@ -5676,10 +6120,12 @@
     char partner_id_buf[INT64_HEX_STRING_MAX];
     char dataset_id_buf[INT64_HEX_STRING_MAX];
     char xpanid_buf[INT64_HEX_STRING_MAX];
+    char priority_buf[INT64_HEX_STRING_MAX];
     srp_server_t *server_state = domain->server_state;
 
     if (domain->srpl_opstate != SRPL_OPSTATE_ROUTINE) {
-        goto exit;
+        INFO(PUB_S_SRP ": not in routine state", domain->name);
+        return;
     }
 
     TXTRecordCreate(&txt_record, 0, NULL);
@@ -5711,6 +6157,13 @@
         goto exit;
     }
 
+    snprintf(priority_buf, sizeof(priority_buf), "%" PRIx32, domain->server_state->priority);
+    err = TXTRecordSetValue(&txt_record, "priority", strlen(priority_buf), priority_buf);
+    if (err != kDNSServiceErr_NoError) {
+        ERROR("unable to set priority in TXT record for _srpl-tls._tcp to " PUB_S_SRP, priority_buf);
+        goto exit;
+    }
+
     // If there is already a registration, get rid of it
     if (domain->srpl_advertise_txn != NULL) {
         ioloop_dnssd_txn_cancel(domain->srpl_advertise_txn);
@@ -5732,6 +6185,7 @@
         goto exit;
     }
     sdref = NULL; // srpl_advertise_txn holds the reference.
+    INFO(PUB_S_SRP ": successfully advertised", domain->name);
 
 exit:
     if (sdref != NULL) {
@@ -5786,44 +6240,96 @@
         ioloop_wakeup_release(domain->partner_discovery_timeout);
         domain->partner_discovery_timeout = NULL;
     }
-
     srpl_maybe_sync_or_transition(domain);
 }
 
+// count how many partners are advertising the proposed dataset id
+static int
+srpl_active_winning_partners(srpl_domain_t *domain, int *rank)
+{
+    int num_winners = 0;
+    int my_rank = 0;
+    if (domain->have_dataset_id) {
+        uint64_t proposed_dataset_id = domain->dataset_id;
+        uint32_t my_priority = domain->server_state->priority;
+        // winning partners are those with higher priority, or same priority but
+        // lower partner id, and advertising the proposed dataset id.
+        for (srpl_instance_t *instance = domain->instances; instance != NULL; instance = instance->next) {
+            if (instance->connection != NULL &&
+                instance->connection->state > srpl_state_session_evaluate &&
+                instance->dataset_id == proposed_dataset_id &&
+                instance->priority >= my_priority)
+            {
+                num_winners++;
+                if (instance->priority > my_priority || (instance->priority == my_priority &&
+                    instance->partner_id < domain->partner_id))
+                {
+                    my_rank++;
+                }
+            }
+        }
+    }
+    *rank = my_rank;
+    return num_winners;
+}
+
+static void
+srpl_sync_with_instance(srpl_instance_t *instance)
+{
+    INFO("sync with " PRI_S_SRP " with dataset_id %" PRIx64, instance->instance_name, instance->dataset_id);
+    if (instance->discovered_in_window) {
+        instance->sync_to_join = true;
+    }
+    instance->sync_fail = false;
+    srpl_event_t event;
+    srpl_event_initialize(&event, srpl_event_do_sync);
+    srpl_event_deliver(instance->connection, &event);
+}
+
 // We check how many active srp servers we discovered. If less than 5, we first
 // check if we are ready to enter the routine state. If there are still srp servers
 // that we haven't started wo sync with, we do so.
 static void
 srpl_maybe_sync_or_transition(srpl_domain_t *domain)
 {
-    int num_peers = 0;
-
-    for (srpl_instance_t *instance = domain->instances; instance != NULL; instance = instance->next) {
-        if (instance->connection != NULL &&
-            instance->connection->state > srpl_state_session_evaluate)
-        {
-            num_peers++;
-        }
+    int num_winners = 0;
+    int rank = 0;
+    // if we haven't committed a dataset id, here we check if we
+    // should propose a new one. we propose a new dataset id if
+    // sync with the instances of the proposed dataset id all fail, or
+    // there's no partner advertising the proposed dataset id.
+    if (!domain->dataset_id_committed) {
+        srpl_maybe_propose_new_dataset_id(domain);
     }
+    num_winners = srpl_active_winning_partners(domain, &rank);
 
-    if (num_peers < MAX_ANYCAST_NUM) {
-        INFO("%d other srp servers are advertising.", num_peers);
+    if (num_winners < MAX_ANYCAST_NUM) {
+        INFO("%d other srp servers are advertising.", num_winners);
+        for (srpl_instance_t *instance = domain->instances; instance != NULL; instance = instance->next) {
+            // we sync with the instances with the same dataset id
+            if (instance->connection != NULL &&
+                instance->connection->state == srpl_state_sync_wait &&
+                srpl_dataset_id_compare(instance->dataset_id, domain->dataset_id) >= 0)
+            {
+                srpl_sync_with_instance(instance);
+            }
+        }
         if (domain->srpl_opstate != SRPL_OPSTATE_ROUTINE &&
             srpl_can_transition_to_routine_state(domain))
         {
             srpl_transition_to_routine_state(domain);
         }
-        for (srpl_instance_t *instance = domain->instances; instance != NULL; instance = instance->next) {
-            if (instance->connection != NULL &&
-                instance->connection->state == srpl_state_sync_wait)
-            {
-                srpl_event_t event;
-                srpl_event_initialize(&event, srpl_event_do_sync);
-                srpl_event_deliver(instance->connection, &event);
-            }
-        }
     } else {
-        INFO("%d other srp servers are advertising.", num_peers);
+        // MAX_ANYCAST_NUM or more better qualified servers are advertising. if
+        // we are in the routine state and advertising, we should withdraw and
+        // reenter the startup state.
+        INFO("%d other srp servers are advertising, rank = %d.", num_winners, rank);
+        if (domain->srpl_opstate == SRPL_OPSTATE_ROUTINE &&
+            rank >= MAX_ANYCAST_NUM)
+        {
+            INFO("transition to startup state.");
+            srpl_transition_to_startup_state(domain);
+        }
     }
 }
 
@@ -5833,7 +6339,7 @@
     srp_server_t *server_state = domain->server_state;
 
     // stop advertising the domain.
-    srpl_stop_srpl_domain_advertisement(domain);
+    srpl_stop_domain_advertisement(domain);
     // move to "startup" state.
     domain->srpl_opstate = SRPL_OPSTATE_STARTUP;
 
@@ -5855,24 +6361,15 @@
     }
     if (domain->partner_discovery_timeout) {
         ioloop_add_wake_event(domain->partner_discovery_timeout, domain,
-                              srpl_partner_discovery_timeout, NULL,
+                              srpl_partner_discovery_timeout, srpl_domain_context_release,
                               MIN_PARTNER_DISCOVERY_INTERVAL +
                               srp_random16() % PARTNER_DISCOVERY_INTERVAL_RANGE);
+        RETAIN_HERE(domain, srpl_domain);
     } else {
         ERROR("unable to add wakeup event for partner discovery.");
         return;
     }
     domain->partner_discovery_pending = true;
-
-    // Existing connections represent a previous dataset ID. We need to resynchronize with these
-    // instances because we have a new dataset ID.
-    for (srpl_instance_t *instance = domain->instances; instance != NULL; instance = instance->next) {
-        if (instance->connection != NULL) {
-            srpl_connection_discontinue(instance->connection);
-            RELEASE_HERE(instance->connection, srpl_connection);
-            instance->connection = NULL;
-        }
-    }
 }
 
 void
diff --git a/ServiceRegistration/srp-replication.h b/ServiceRegistration/srp-replication.h
index 73fc41d..8ed74fa 100644
--- a/ServiceRegistration/srp-replication.h
+++ b/ServiceRegistration/srp-replication.h
@@ -103,6 +103,11 @@
     srpl_state_session_message_wait,
     srpl_state_session_response_send,
     srpl_state_send_candidates_message_wait,
+
+#ifdef SRP_TEST_SERVER
+    // States for testing
+    srpl_state_test_event_intercept,
+#endif
 };
 
 enum srpl_event_type {
@@ -143,8 +148,11 @@
 typedef struct srpl_host_update srpl_host_update_t;
 typedef struct srpl_advertise_finished_result srpl_advertise_finished_result_t;
 typedef struct srpl_session srpl_session_t;
+#ifdef SRP_TEST_SERVER
+typedef struct test_packet_state test_packet_state_t;
+#endif
 
-typedef void (*address_change_callback_t)(void *NULLABLE context, addr_t *NULLABLE address, bool added, int err);
+typedef void (*address_change_callback_t)(void *NULLABLE context, addr_t *NULLABLE address, bool added, bool more, int err);
 typedef void (*address_query_cancel_callback_t)(void *NULLABLE context);
 typedef enum {
     address_query_next_address_gotten, // success
@@ -259,19 +267,30 @@
     char *NONNULL name;
     char *NONNULL state_name;
     comm_t *NULLABLE connection;
+    const char *NULLABLE connection_null_reason; // for debugging, records why we NULLed connection.
+    struct timeval connection_null_time; // When connection was set to NULL
     addr_t connected_address;
     srpl_candidate_t *NULLABLE candidate;
     dso_state_t *NULLABLE dso;
     srpl_instance_t *NULLABLE instance;
     wakeup_t *NULLABLE reconnect_wakeup;
+    wakeup_t *NULLABLE state_timeout; // how long the srpl connecton could stay in a state before we assume it's gone.
     message_t *NULLABLE message;
     adv_host_t *NULLABLE *NULLABLE candidates;
     srpl_host_update_t stashed_host;
     srpl_srp_client_queue_entry_t *NULLABLE client_update_queue;
     wakeup_t *NULLABLE keepalive_send_wakeup;
     wakeup_t *NULLABLE keepalive_receive_wakeup;
+#ifdef SRP_TEST_SERVER
+    void (*NULLABLE advertise_finished_callback)(test_state_t *NONNULL state);
+    void (*NULLABLE srpl_advertise_finished_callback)(srpl_connection_t *NONNULL connection);
+    test_state_t *NULLABLE test_state;
+    srpl_connection_t *NULLABLE next;
+    srp_server_t *NONNULL server;
+#endif
     time_t last_message_sent;
     time_t last_message_received;
+    time_t state_start_time;
     int num_candidates;
     int current_candidate;
     int retry_delay; // How long to send when we send a retry_delay message
@@ -324,12 +343,19 @@
     srpl_instance_service_t *NONNULL services;
     uint64_t partner_id;
     uint64_t dataset_id;
+    uint32_t priority;
     bool have_partner_id;
     bool have_dataset_id;
+    bool have_priority;
     bool sync_to_join;  // True if sync with the remote partner is required to join the replication
+    bool sync_fail;     // True if sync with the remote partner is declared fail
+    bool discovered_in_window; // True if the instance is discovered in partner discovery window
     bool is_me;
     bool discontinuing; // True if we are in the process of discontinuing this instance.
     bool unmatched; // True if this is an incoming connection that hasn't been associated with a real instance.
+    bool matched_unidentified; // True if an address from address callback matches an unidentified connection
+    bool added_address; // True if address callback adds an address to the instance
+    bool version_mismatch; // True if the version mismatches
 };
 
 typedef enum {
@@ -341,6 +367,7 @@
     uint64_t partner_id; // SRP replication partner ID
     uint64_t dataset_id;
     bool have_dataset_id;
+    bool dataset_id_committed;
     bool partner_discovery_pending;
     int ref_count;
     srpl_opstate_t srpl_opstate;
@@ -395,16 +422,24 @@
 #define LOWER56_BIT_MASK 0xFFFFFFFFFFFFFFULL
 
 // SRP Replication protocol versioning
-#define SRPL_VERSION_MULTI_HOST_MESSAGE     1
-#define SRPL_VERSION_ANYCAST                2
-#define SRPL_CURRENT_VERSION                SRPL_VERSION_ANYCAST
+// Protocol version number 1: outdated and no longer being used. This version was supposed to
+//                            support multi host messages but did not really work. After making
+//                            it work, we increment the version number to 3.
+// Protocol version number 2: to support anycast service
+// Protocol version number 3: to support multi host messages
+#define SRPL_VERSION_ANYCAST                    2
+#define SRPL_VERSION_MULTI_HOST_MESSAGE         3
+#define SRPL_VERSION_EDNS0_TSR                  4
+#define SRPL_CURRENT_VERSION                    SRPL_VERSION_EDNS0_TSR
 
 // Variation bits.
 #define SRPL_VARIATION_MULTI_HOST_MESSAGE   1
 #define SRPL_SUPPORTS(srpl_connection, variation) \
-    (((srpl_connection)->variation_mask & ~(variation)) != 0)
+    (((srpl_connection)->variation_mask & (variation)) != 0)
 
 // Exported functions...
+srpl_connection_t *NULLABLE srpl_connection_create(srpl_instance_t *NONNULL instance, bool outgoing);
+void srpl_connection_next_state(srpl_connection_t *NONNULL srpl_connection, srpl_state_t state);
 void srpl_startup(srp_server_t *NONNULL srp_server);
 void srpl_shutdown(srp_server_t *NONNULL server_state);
 void srpl_disable(srp_server_t *NONNULL srp_server);
@@ -420,6 +455,9 @@
 void srpl_connection_release_(srpl_connection_t *NONNULL srpl_connection, const char *NONNULL file, int line);
 #define srpl_connection_retain(connection) srpl_connection_retain_(connection, __FILE__, __LINE__)
 void srpl_connection_retain_(srpl_connection_t *NONNULL srpl_connection, const char *NONNULL file, int line);
+srpl_domain_t *NULLABLE srpl_domain_create_or_copy(srp_server_t *NONNULL server_state, const char *NONNULL domain_name);
+void srpl_dump_connection_states(srp_server_t *NONNULL server_state);
+void srpl_change_server_priority(srp_server_t *NONNULL server_state, uint32_t new);
 #endif // __SRP_REPLICATION_H__
 
 // Local Variables:
diff --git a/ServiceRegistration/srp.h b/ServiceRegistration/srp.h
index 0cd7bbc..89a5d5b 100644
--- a/ServiceRegistration/srp.h
+++ b/ServiceRegistration/srp.h
@@ -1,12 +1,12 @@
 /* srp.h
  *
- * Copyright (c) 2018-2023 Apple Computer, Inc. All rights reserved.
+ * Copyright (c) 2018-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
- *     http://www.apache.org/licenses/LICENSE-2.0
+ *     https://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
@@ -40,6 +40,7 @@
 
 #include "srp-features.h"           // for feature flags
 
+
 #ifdef __clang__
 #define NULLABLE _Nullable
 #define NONNULL _Nonnull
@@ -295,13 +296,51 @@
 static inline bool
 is_thread_mesh_anycast_address(const struct in6_addr *addr)
 {
-    // Thread 1.3.0RC3 section 5.2.2.2 Anycast Locator (ALOC)
+    // Thread 1.3.0 section 5.2.2.2 Anycast Locator (ALOC)
     if (!memcmp(&addr->s6_addr[8], thread_anycast_preamble, sizeof(thread_anycast_preamble))) {
         return true;
     }
     return false;
 }
 
+static inline bool
+is_thread_mesh_rloc_address(const struct in6_addr *addr)
+{
+    // Thread 1.3.0 section 5.2.2.1 Routing Locator (RLOC)
+    if (!memcmp(&addr->s6_addr[8], thread_rloc_preamble, sizeof(thread_rloc_preamble))) {
+        return true;
+    }
+    return false;
+}
+
+static inline bool
+is_thread_mesh_synthetic_address(const struct in6_addr *addr)
+{
+    return is_thread_mesh_anycast_address(addr) || is_thread_mesh_rloc_address(addr);
+}
+
+static inline bool
+is_thread_mesh_synthetic_or_link_local(const struct in6_addr *addr)
+{
+    return (is_thread_mesh_anycast_address(addr) || is_thread_mesh_rloc_address(addr) ||
+            (addr->s6_addr[0] == 0xfe && (addr->s6_addr[1] & 0xc0) == 0x80));
+}
+
+#ifndef _SRP_STRICT_DISPOSE_TEMPLATE
+    #define _SRP_STRICT_DISPOSE_TEMPLATE(PTR, FUNCTION) \
+        do {                                            \
+            if (*(PTR) != NULL) {                       \
+                FUNCTION(*PTR);                         \
+                *(PTR) = NULL;                          \
+            }                                           \
+        } while(0)
+#endif
+
+#ifndef DNSServiceRefSourceForget
+    #define DNSServiceRefSourceForget(PTR) _SRP_STRICT_DISPOSE_TEMPLATE(PTR, DNSServiceRefDeallocate)
+#endif
+
+
 /*!
  *  @brief
  *      Check the required condition, if the required condition is not met go to the label specified.
diff --git a/ServiceRegistration/state-machine.c b/ServiceRegistration/state-machine.c
index 8442258..82949f6 100644
--- a/ServiceRegistration/state-machine.c
+++ b/ServiceRegistration/state-machine.c
@@ -1,6 +1,6 @@
 /* state-machine.h
  *
- * Copyright (c) 2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2023-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -130,6 +130,13 @@
     EVENT_NAME_DECL(probe_completed),
     EVENT_NAME_DECL(got_mesh_local_prefix),
     EVENT_NAME_DECL(daemon_disconnect),
+    EVENT_NAME_DECL(stop),
+    EVENT_NAME_DECL(dns_registration_invalidated),
+    EVENT_NAME_DECL(thread_interface_changed),
+    EVENT_NAME_DECL(wed_ml_eid_changed),
+    EVENT_NAME_DECL(neighbor_ml_eid_changed),
+    EVENT_NAME_DECL(srp_needed),
+    EVENT_NAME_DECL(dns_registration_bad_service),
 };
 #define STATE_MACHINE_NUM_EVENT_TYPES (sizeof(state_machine_event_configurations) / sizeof(state_machine_event_configuration_t))
 
@@ -245,6 +252,12 @@
 	return true;
 }
 
+void state_machine_cancel(state_machine_header_t *NONNULL state_header)
+{
+    INFO("canceling " PUB_S_SRP, state_header->name);
+    state_header->state = state_machine_state_invalid;
+}
+
 // Local Variables:
 // mode: C
 // tab-width: 4
diff --git a/ServiceRegistration/state-machine.h b/ServiceRegistration/state-machine.h
index 724ed61..f7a5a26 100644
--- a/ServiceRegistration/state-machine.h
+++ b/ServiceRegistration/state-machine.h
@@ -1,6 +1,6 @@
 /* state-machine.h
  *
- * Copyright (c) 2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2023-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -21,22 +21,22 @@
 #ifndef __STATE_MACHINE_H__
 #define __STATE_MACHINE_H__ 1
 
-#define RELEASE_RETAIN_FUNCS(type)                                            \
-void                                                                          \
-type##_retain(type##_t *NONNULL omw)                                          \
-{                                                                             \
-    RETAIN_HERE(omw, type);                                                   \
-}                                                                             \
-                                                                              \
-void                                                                          \
-type##_release(type##_t *NONNULL omw)                                         \
-{                                                                             \
-    RELEASE_HERE(omw, type);                                                  \
+#define RELEASE_RETAIN_FUNCS(type)                                              \
+void                                                                            \
+type##_retain_(type##_t * omw, const char *file, int line)                      \
+{                                                                               \
+    RETAIN(omw, type);                                                          \
+}                                                                               \
+                                                                                \
+void                                                                            \
+type##_release_(type##_t *NONNULL omw, const char *file, int line)              \
+{                                                                               \
+    RELEASE(omw, type);                                                         \
 }
 
-#define RELEASE_RETAIN_DECLS(type)                                            \
-void type##_retain(type##_t *NONNULL omw);                                    \
-void type##_release(type##_t *NONNULL omw)
+#define RELEASE_RETAIN_DECLS(type)                                              \
+void type##_retain_(type##_t *NONNULL omw, const char *NONNULL file, int line); \
+void type##_release_(type##_t *NONNULL omw, const char *NONNULL file, int line);
 
 // The assumptions below are that every object that holds a state that these macros can operate on has
 // the following elements:
@@ -135,6 +135,13 @@
     state_machine_event_type_probe_completed,
     state_machine_event_type_got_mesh_local_prefix,
     state_machine_event_type_daemon_disconnect,
+    state_machine_event_type_stop,
+    state_machine_event_type_dns_registration_invalidated,
+    state_machine_event_type_thread_interface_changed,
+    state_machine_event_type_wed_ml_eid_changed,
+    state_machine_event_type_neighbor_ml_eid_changed,
+    state_machine_event_type_srp_needed,
+    state_machine_event_type_dns_registration_bad_service,
 } state_machine_event_type_t;
 
 typedef struct state_machine_event state_machine_event_t;
@@ -190,6 +197,7 @@
 void state_machine_event_deliver(state_machine_header_t *NONNULL state_header, state_machine_event_t *NONNULL event);
 bool state_machine_header_setup(state_machine_header_t *NONNULL state_header, void *NONNULL state_object, const char *NULLABLE name,
                                 state_machine_type_t type, state_machine_decl_t *NONNULL states, size_t num_states);
+void state_machine_cancel(state_machine_header_t *NONNULL state_header);
 #endif // __STATE_MACHINE_H__
 
 // Local Variables:
diff --git a/ServiceRegistration/test/srp-test-runner.c b/ServiceRegistration/test/srp-test-runner.c
new file mode 100644
index 0000000..e3b819d
--- /dev/null
+++ b/ServiceRegistration/test/srp-test-runner.c
@@ -0,0 +1,144 @@
+/* srp-test-runner.h
+ *
+ * Copyright (c) 2023 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains the SRP server test runner.
+ */
+
+#include "srp.h"
+#include <dns_sd.h>
+#include "srp-test-runner.h"
+#include "srp-api.h"
+#include "dns-msg.h"
+#include "ioloop.h"
+#include "srp-mdns-proxy.h"
+#include "test-api.h"
+#include "srp-proxy.h"
+#include "srp-mdns-proxy.h"
+#include "test-dnssd.h"
+#include "test.h"
+
+ready_callback_t srp_test_dnssd_tls_listener_ready;
+void *srp_test_tls_listener_context;
+void (*srp_test_dso_message_finished)(void *context, message_t *message, dso_state_t *dso);
+
+void
+srp_test_server_run_test(void *context)
+{
+    char *test_to_run = context;
+
+    if (test_to_run != NULL) {
+#if TARGET_OS_IOS || TARGET_OS_OSX || TARGET_OS_TV
+        if (!strcmp(test_to_run, "change-text-record")) {
+            test_change_text_record_start(NULL);
+        } else if (!strcmp(test_to_run, "multi-host-record")) {
+            test_multi_host_record_start(NULL);
+        } else if (!strcmp(test_to_run, "lease-expiry")) {
+            test_lease_expiry_start(NULL);
+        } else if (!strcmp(test_to_run, "lease-renewal")) {
+            test_lease_renewal_start(NULL);
+        } else if (!strcmp(test_to_run, "single-srpl-update")) {
+            test_single_srpl_update(NULL);
+        } else if (!strcmp(test_to_run, "srpl-two-instances-dup")) {
+            test_srpl_host_2i(NULL, DUP_TEST_VARIANT_BOTH);
+        } else if (!strcmp(test_to_run, "srpl-two-instances-dup-first")) {
+            test_srpl_host_2i(NULL, DUP_TEST_VARIANT_FIRST);
+        } else if (!strcmp(test_to_run, "srpl-two-instances-dup-last")) {
+            test_srpl_host_2i(NULL, DUP_TEST_VARIANT_LAST);
+        } else if (!strcmp(test_to_run, "srpl-two-instances-dup-add-first")) {
+            test_srpl_host_2i(NULL, DUP_TEST_VARIANT_FIRST);
+        } else if (!strcmp(test_to_run, "srpl-two-instances-dup-add-last")) {
+            test_srpl_host_2i(NULL, DUP_TEST_VARIANT_LAST);
+        } else if (!strcmp(test_to_run, "srpl-two-instances-dup-2keys")) {
+            test_srpl_host_2i(NULL, DUP_TEST_VARIANT_TWO_KEYS);
+        } else if (!strcmp(test_to_run, "srpl-two-instances")) {
+            test_srpl_host_2i(NULL, DUP_TEST_VARIANT_NO_DUP);
+        } else if (!strcmp(test_to_run, "srpl-two-instances-one-remove")) {
+            test_srpl_host_2ir(NULL);
+        } else if (!strcmp(test_to_run, "srpl-zero-instances-two-servers")) {
+            test_srpl_host_0i2s(NULL);
+        } else if (!strcmp(test_to_run, "srpl-lease-time")) {
+            test_srpl_lease_time(NULL);
+        } else if (!strcmp(test_to_run, "dns-dangling-query")) {
+            test_dns_dangling_query(NULL);
+        } else if (!strcmp(test_to_run, "srpl-cycle-through-peers")) {
+            test_srpl_cycle_through_peers(NULL);
+        } else if (!strcmp(test_to_run, "srpl-update-after-remove")) {
+            test_srpl_update_after_remove(NULL);
+        } else if (!strcmp(test_to_run, "dns-push-hardwired")) {
+            test_dns_push(NULL, PUSH_TEST_VARIANT_HARDWIRED);
+        } else if (!strcmp(test_to_run, "dns-push-mdns")) {
+            test_dns_push(NULL, PUSH_TEST_VARIANT_MDNS);
+        } else if (!strcmp(test_to_run, "dns-hardwired")) {
+            test_dns_push(NULL, PUSH_TEST_VARIANT_DNS_HARDWIRED);
+        } else if (!strcmp(test_to_run, "dns-mdns")) {
+            test_dns_push(NULL, PUSH_TEST_VARIANT_DNS_MDNS);
+        } else if (!strcmp(test_to_run, "dns-push-crash")) {
+            test_dns_push(NULL, PUSH_TEST_VARIANT_DAEMON_CRASH);
+        } else if (!strcmp(test_to_run, "dns-crash")) {
+            test_dns_push(NULL, PUSH_TEST_VARIANT_DNS_CRASH);
+        } else if (!strcmp(test_to_run, "dns-two")) {
+            test_dns_push(NULL, PUSH_TEST_VARIANT_TWO_QUESTIONS);
+        } else if (!strcmp(test_to_run, "listener-longevity")) {
+            test_listen_longevity_start(NULL);
+        } else if (!strcmp(test_to_run, "ifaddrs")) {
+            test_ifaddrs_start(NULL);
+        } else {
+            INFO("test to run: %s", test_to_run);
+            exit(1);
+        }
+#else
+        INFO("skipping test %s on limited device.");
+        exit(0);
+#endif
+    } else {
+        INFO("no test to run");
+        exit(1);
+    }
+}
+
+void *
+srp_test_server_find_instance(void *state, const char *name, const char *regtype)
+{
+    test_state_t *test_state = (test_state_t *)state;
+    srp_server_t *server = test_state->primary;
+    adv_host_t *host;
+    adv_instance_t *instance;
+
+    for (host = server->hosts; host != NULL; host = host->next) {
+        if (host->instances != NULL) {
+            for (int i = 0; i < host->instances->num; i++) {
+                if (host->instances->vec[i] != NULL) {
+                    instance = host->instances->vec[i];
+                    if (!strcmp(instance->instance_name, name) &&
+                        !strcmp(instance->service_type, regtype))
+                    {
+                        return instance;
+                    }
+                }
+            }
+        }
+    }
+    return NULL;
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/test/srp-test-runner.h b/ServiceRegistration/test/srp-test-runner.h
new file mode 100644
index 0000000..a4eb269
--- /dev/null
+++ b/ServiceRegistration/test/srp-test-runner.h
@@ -0,0 +1,41 @@
+/* srp-test-runner.h
+ *
+ * Copyright (c) 2019-2023 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains the SRP Advertising Proxy, which is an SRP Server
+ * that offers registered addresses using mDNS.
+ */
+
+DNSServiceErrorType srp_client_update_record(DNSServiceRef NONNULL sdRef, DNSRecordRef NULLABLE RecordRef,
+                                             DNSServiceFlags flags, uint16_t rdlen, const void *NONNULL rdata,
+                                             uint32_t ttl);
+DNSServiceErrorType srp_client_register(DNSServiceRef NULLABLE *NONNULL sdRef, DNSServiceFlags flags,
+                                        uint32_t interfaceIndex, const char *NULLABLE name,
+                                        const char *NULLABLE regtype, const char *NULLABLE domain,
+                                        const char *NULLABLE host, uint16_t port, uint16_t txtLen,
+                                        const void *NONNULL txtRecord, DNSServiceRegisterReply NONNULL callBack,
+                                        void *NULLABLE context);
+void srp_client_ref_deallocate(DNSServiceRef NONNULL sdRef);
+void srp_test_server_run_test(void *NULLABLE context);
+void *NULLABLE srp_test_server_find_instance(void *NONNULL state, const char *NONNULL name, const char *NONNULL regtype);
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/test/test-api.c b/ServiceRegistration/test/test-api.c
new file mode 100644
index 0000000..0d4a6d4
--- /dev/null
+++ b/ServiceRegistration/test/test-api.c
@@ -0,0 +1,456 @@
+/* test-api.c
+ *
+ * Copyright (c) 2023-2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * srp host API test harness
+ */
+
+#include <stdio.h>
+#include <arpa/inet.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <dns_sd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include "srp.h"
+#include "srp-api.h"
+#include "dns-msg.h"
+#include "srp-crypto.h"
+#include "ioloop.h"
+#include "dso-utils.h"
+#include "dso.h"
+
+#include "cti-services.h"
+#include "test-api.h"
+#include "srp-proxy.h"
+#include "srp-mdns-proxy.h"
+#include "dnssd-proxy.h"
+#include "route.h"
+
+#define SRP_IO_CONTEXT_MAGIC 0xFEEDFACEFADEBEEFULL  // BEES!   Everybody gets BEES!
+typedef struct io_context {
+    uint64_t magic_cookie1;
+    wakeup_t *wakeup;
+    void *NONNULL srp_context;
+    void *NONNULL host_context;
+    comm_t *NULLABLE connection;
+    srp_wakeup_callback_t wakeup_callback;
+    srp_datagram_callback_t datagram_callback;
+    bool deactivated, closed;
+    uint64_t magic_cookie2;
+} io_context_t;
+
+// For testing signature with a time that's out of range.
+static bool test_bad_sig_time;
+// For testing a signature that doesn't validate
+static bool invalidate_signature;
+
+bool
+configure_dnssd_proxy(void)
+{
+    extern srp_server_t *srp_servers;
+    if (srp_servers->test_state != NULL && srp_servers->test_state->dnssd_proxy_configurer != NULL) {
+        return srp_servers->test_state->dnssd_proxy_configurer();
+    } else {
+        dnssd_proxy_udp_port= 53;
+        dnssd_proxy_tcp_port = 53;
+        dnssd_proxy_tls_port = 853;
+        return true;
+    }
+}
+
+int
+srp_test_getifaddrs(srp_server_t *server_state, struct ifaddrs **ifaddrs, void *context)
+{
+    if (server_state->test_state != NULL && server_state->test_state->getifaddrs != NULL) {
+        return server_state->test_state->getifaddrs(server_state, ifaddrs, context);
+    }
+    return getifaddrs(ifaddrs);
+}
+
+void
+srp_test_freeifaddrs(srp_server_t *server_state, struct ifaddrs *ifaddrs, void *context)
+{
+    if (server_state->test_state != NULL && server_state->test_state->freeifaddrs != NULL) {
+        server_state->test_state->freeifaddrs(server_state, ifaddrs, context);
+        return;
+    }
+    freeifaddrs(ifaddrs);
+}
+
+void
+srp_test_state_add_timeout(test_state_t *state, int timeout)
+{
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * timeout), dispatch_get_main_queue(), ^{
+            if (!state->test_complete) {
+                TEST_FAIL(state, "test failed: timeout");
+                exit(1);
+            }
+        });
+}
+
+void
+srp_test_state_next(test_state_t *state)
+{
+    if (state->next != NULL) {
+        test_state_t *next_state = state->next;
+        next_state->test_complete = true;
+        next_state->finished_tests = state;
+        if (next_state->continue_testing == NULL) {
+            TEST_FAIL(next_state, "no continue function");
+        }
+        next_state->continue_testing(next_state);
+    } else {
+        exit(0);
+    }
+}
+
+void
+srp_test_state_explain(test_state_t *state)
+{
+    if (state != NULL) {
+        if (state->variant_title != NULL) {
+            fprintf(stderr, "\n%s (%s variant)\n", state->title, state->variant_title);
+        } else {
+            fprintf(stderr, "\n%s\n", state->title);
+        }
+        fprintf(stderr, "\n%s\n\n", state->explanation);
+        if (state->variant_info != NULL) {
+            fprintf(stderr, "Variant: %s\n\n", state->variant_info);
+        }
+    }
+}
+
+test_state_t *
+test_state_create(srp_server_t *primary, const char *title, const char *variant_title,
+                  const char *explanation, const char *variant_info)
+{
+    test_state_t *ret = calloc(1, sizeof(*ret));
+    TEST_FAIL_CHECK(NULL, ret != NULL, "no memory for test state");
+    ret->primary = primary;
+    primary->test_state = ret;
+    ret->title = title;
+    ret->variant_title = variant_title;
+    ret->explanation = explanation; // Explanation is assumed to be a compile-time constant string.
+    ret->variant_info = variant_info;
+    return ret;
+}
+
+void
+srp_test_set_local_example_address(test_state_t *UNUSED state)
+{
+    static const uint8_t ifaddr[] = {
+        0x20, 1, 0xd, 0xb8, // 2001:0db8:
+        0, 0, 0, 0, // /64 prefix
+        0, 0, 0, 0,
+        0, 0, 0, 1, // 2001:db8::1
+    };
+    srp_add_interface_address(dns_rrtype_aaaa, ifaddr, sizeof(ifaddr));
+}
+
+void
+srp_test_network_localhost_start(test_state_t *UNUSED state)
+{
+    static const uint8_t localhost[] = {
+        0, 0, 0, 0,
+        0, 0, 0, 0, // /64 prefix
+        0, 0, 0, 0,
+        0, 0, 0, 1, // ::1
+    };
+    static const uint8_t port[] = { 0, 53 };
+
+    srp_add_server_address(port, dns_rrtype_aaaa, localhost, sizeof(localhost));
+    srp_test_set_local_example_address(state);
+
+    srp_network_state_stable(NULL);
+}
+
+bool
+srp_get_last_server(uint16_t *NONNULL UNUSED rrtype, uint8_t *NONNULL UNUSED rdata, uint16_t UNUSED rdlim,
+                    uint8_t *NONNULL UNUSED port, void *NULLABLE UNUSED host_context)
+{
+    return false;
+}
+
+bool
+srp_save_last_server(uint16_t UNUSED rrtype, uint8_t *UNUSED rdata, uint16_t UNUSED rdlength,
+                     uint8_t *UNUSED port, void *UNUSED host_context)
+{
+    return false;
+}
+
+static int
+validate_io_context(io_context_t **dest, void *src)
+{
+    io_context_t *context = src;
+    if (context->magic_cookie1 == SRP_IO_CONTEXT_MAGIC &&
+        context->magic_cookie2 == SRP_IO_CONTEXT_MAGIC)
+   {
+        *dest = context;
+        return kDNSServiceErr_NoError;
+    }
+    return kDNSServiceErr_BadState;
+}
+
+int
+srp_deactivate_udp_context(void *host_context, void *in_context)
+{
+    io_context_t *io_context;
+    int err;
+    (void)host_context;
+
+    err = validate_io_context(&io_context, in_context);
+    if (err == kDNSServiceErr_NoError) {
+        if (io_context->wakeup != NULL) {
+            ioloop_cancel_wake_event(io_context->wakeup);
+            ioloop_wakeup_release(io_context->wakeup);
+        }
+        // Deactivate can be called with a connection still active; in this case, we need to wait for the
+        // cancel event before freeing the structure. Otherwise, we can free it immediately.
+        if (io_context->connection != NULL) {
+            ioloop_comm_cancel(io_context->connection);
+            io_context->deactivated = true;
+            io_context->closed = true;
+        } else {
+            free(io_context);
+        }
+    }
+    return err;
+}
+
+int
+srp_disconnect_udp(void *context)
+{
+    io_context_t *io_context;
+    int err;
+
+    err = validate_io_context(&io_context, context);
+    if (err == kDNSServiceErr_NoError) {
+        if (io_context->wakeup != NULL) {
+            ioloop_cancel_wake_event(io_context->wakeup);
+        }
+        if (io_context->connection) {
+            io_context->connection = NULL;
+        }
+        io_context->closed = true;
+    }
+    return err;
+}
+
+static bool
+srp_test_send_intercept(comm_t *connection, message_t *UNUSED responding_to,
+                        struct iovec *iov, int iov_len, bool UNUSED final, bool send_length)
+{
+    bool send_length_real = send_length;
+    srp_server_t *srp_server = connection->test_context;
+    test_state_t *test_state = srp_server->test_state;
+    io_context_t *current_io_context = test_state->current_io_context;
+    TEST_FAIL_CHECK(test_state, test_state != NULL, "no test state");
+    TEST_FAIL_CHECK(test_state, current_io_context != NULL, "no I/O state");
+    TEST_FAIL_CHECK(test_state, current_io_context->datagram_callback != NULL, "no datagram callback");
+
+    // Don't copy if we don't have to.
+    if (!send_length && iov_len == 1) {
+        size_t len = iov[0].iov_len;
+        uint8_t *data = calloc(1, len);
+        memcpy(data, iov[0].iov_base, len);
+        dispatch_async(dispatch_get_main_queue(), ^{
+                current_io_context->datagram_callback(current_io_context, data, len);
+                free(data);
+            });
+        return true;
+    }
+
+    // send_length indicates whether we should send a length over TCP, not whether we should send a length.
+    if (!connection->tcp_stream) {
+        send_length_real = false;
+    }
+
+    // We have an actual iov, or need to prepend a length, so we have to allocate and copy.
+    uint8_t *message;
+    size_t length = send_length_real ? 2 : 0;
+    uint8_t *mp;
+    for (int i = 0; i < iov_len; i++) {
+        length += iov[i].iov_len;
+    }
+
+    message = malloc(length);
+    TEST_FAIL_CHECK(test_state, message != NULL, "no memory for message");
+    mp = message;
+    // Marshal all the data into a single buffer.
+    if (send_length_real) {
+        *mp++ = length >> 8;
+        *mp++ = length & 0xff;
+    }
+    for (int i = 0; i < iov_len; i++) {
+        memcpy(mp, iov[i].iov_base, iov[i].iov_len);
+        mp += iov[i].iov_len;
+    }
+
+    dispatch_async(dispatch_get_main_queue(), ^{
+            current_io_context->datagram_callback(current_io_context->srp_context, message, length);
+        });
+    return true;
+}
+
+int
+srp_connect_udp(void *context, const uint8_t *UNUSED port, uint16_t UNUSED address_type,
+                const uint8_t *UNUSED address, uint16_t UNUSED addrlen)
+{
+    io_context_t *io_context;
+    int err;
+
+    err = validate_io_context(&io_context, context);
+    if (err == kDNSServiceErr_NoError) {
+        if (io_context->connection) {
+            ERROR("srp_connect_udp called with non-null I/O context.");
+            return kDNSServiceErr_Invalid;
+        }
+
+        srp_server_t *server_state = io_context->host_context;
+        test_state_t *test_state = server_state->test_state;
+        if (test_state == NULL || test_state->srp_listener == NULL) {
+            return kDNSServiceErr_NotInitialized;
+        }
+        io_context->connection = test_state->srp_listener;
+        test_state->current_io_context = io_context;
+        io_context->connection->test_send_intercept = srp_test_send_intercept;
+        io_context->connection->test_context = server_state;
+    }
+    return err;
+}
+
+int
+srp_make_udp_context(void *host_context, void **p_context, srp_datagram_callback_t callback, void *context)
+{
+    io_context_t *io_context = calloc(1, sizeof *io_context);
+    if (io_context == NULL) {
+        return kDNSServiceErr_NoMemory;
+    }
+    io_context->magic_cookie1 = io_context->magic_cookie2 = SRP_IO_CONTEXT_MAGIC;
+    io_context->datagram_callback = callback;
+    io_context->srp_context = context;
+    io_context->host_context = host_context;
+
+    io_context->wakeup = ioloop_wakeup_create();
+    if (io_context->wakeup == NULL) {
+        free(io_context);
+        return kDNSServiceErr_NoMemory;
+    }
+
+    *p_context = io_context;
+    return kDNSServiceErr_NoError;
+}
+
+static void
+wakeup_callback(void *context)
+{
+    io_context_t *io_context;
+    if (validate_io_context(&io_context, context) == kDNSServiceErr_NoError) {
+        INFO("wakeup on context %p srp_context %p", io_context, io_context->srp_context);
+        INFO("setting wakeup callback %p wakeup %p", io_context->wakeup_callback, io_context->wakeup);
+        if (!io_context->deactivated) {
+            io_context->wakeup_callback(io_context->srp_context);
+        }
+    } else {
+        INFO("wakeup with invalid context: %p", context);
+    }
+}
+
+int
+srp_set_wakeup(void *host_context, void *context, int milliseconds, srp_wakeup_callback_t callback)
+{
+    int err;
+    io_context_t *io_context;
+    (void)host_context;
+
+    err = validate_io_context(&io_context, context);
+    if (err == kDNSServiceErr_NoError) {
+        INFO("setting wakeup callback %p wakeup %p", callback, io_context->wakeup);
+        io_context->wakeup_callback = callback;
+        ioloop_add_wake_event(io_context->wakeup, io_context, wakeup_callback, NULL, milliseconds);
+    }
+    return err;
+}
+
+int
+srp_cancel_wakeup(void *host_context, void *context)
+{
+    int err;
+    io_context_t *io_context;
+    (void)host_context;
+
+    err = validate_io_context(&io_context, context);
+    if (err == kDNSServiceErr_NoError) {
+        ioloop_cancel_wake_event(io_context->wakeup);
+    }
+    return err;
+}
+
+int
+srp_send_datagram(void *host_context, void *context, void *message, size_t message_length)
+{
+    int err;
+    io_context_t *io_context;
+    srp_server_t *srp_server = host_context;
+    test_state_t *test_state = srp_server->test_state;
+
+    if (invalidate_signature) {
+        ((uint8_t *)message)[message_length - 10] = ~(((uint8_t *)message)[message_length - 10]);
+    }
+
+    err = validate_io_context(&io_context, context);
+    if (err == kDNSServiceErr_NoError) {
+        if (io_context->connection == NULL) {
+            return kDNSServiceErr_DefunctConnection;
+        }
+        TEST_FAIL_CHECK(test_state, io_context->connection->datagram_callback != NULL, "srp listener has no datagram callback");
+        message_t *actual = ioloop_message_create(message_length);
+        TEST_FAIL_CHECK(test_state, actual != NULL, "no memory for message");
+        memcpy(&actual->wire, message, message_length);
+        io_context->connection->datagram_callback(io_context->connection, actual, io_context->connection->context);
+        ioloop_message_release(actual);
+    }
+    return err;
+}
+
+uint32_t
+srp_timenow(void)
+{
+    time_t now = time(NULL);
+    if (test_bad_sig_time) {
+        return (uint32_t)(now - 10000);
+    }
+    return (uint32_t)now;
+}
+
+void
+srp_test_enable_stub_router(test_state_t *state, srp_server_t *NONNULL server_state)
+{
+    server_state->route_state = route_state_create(server_state, "srp-mdns-proxy");
+    TEST_FAIL_CHECK(state, server_state->route_state != NULL, "no memory for route state");
+    server_state->stub_router_enabled = true;
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/test/test-api.h b/ServiceRegistration/test/test-api.h
new file mode 100644
index 0000000..43e86c9
--- /dev/null
+++ b/ServiceRegistration/test/test-api.h
@@ -0,0 +1,125 @@
+/* test-api.c
+ *
+ * Copyright (c) 2023-2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * srp host API test harness
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <ifaddrs.h>
+
+typedef struct test_state test_state_t;
+typedef struct io_context io_context_t;
+typedef struct dns_service_event dns_service_event_t;
+typedef struct test_packet_state test_packet_state_t;
+
+extern ready_callback_t NULLABLE srp_test_dnssd_tls_listener_ready;
+extern void *NULLABLE srp_test_tls_listener_context;
+extern void (*NULLABLE srp_test_dso_message_finished)(void *NULLABLE context, message_t *NONNULL message,
+                                                      dso_state_t *NONNULL dso);
+typedef bool (*dns_service_query_record_callback_intercept_t)(DNSServiceRef NONNULL sdRef, DNSServiceFlags flags,
+                                                              uint32_t interfaceIndex, DNSServiceErrorType errorCode,
+                                                              const char *NULLABLE fullname, uint16_t rrtype,
+                                                              uint16_t rrclass, uint16_t rdlen,
+                                                              const void *NULLABLE rdata, uint32_t ttl,
+                                                              void *NULLABLE context);
+
+struct test_state {
+    test_state_t *NULLABLE next, *NULLABLE finished_tests;
+    srp_server_t *NULLABLE primary;
+    comm_t *NULLABLE srp_listener;
+    io_context_t *NULLABLE current_io_context;
+    test_packet_state_t *NULLABLE test_packet_state;
+    dns_service_event_t *NULLABLE dns_service_events;
+    void *NULLABLE context;
+    int counter;
+    const char *NONNULL title;
+    const char *NULLABLE variant_title;
+    const char *NONNULL explanation;
+    const char *NULLABLE variant_info;
+    void (*NULLABLE continue_testing)(test_state_t *NONNULL next_state);
+    bool (*NULLABLE dnssd_proxy_configurer)(void);
+    int (*NULLABLE getifaddrs)(srp_server_t *NULLABLE server_state, struct ifaddrs *NULLABLE *NONNULL ifaddrs,
+                               void *NULLABLE context);
+    void (*NULLABLE freeifaddrs)(srp_server_t *NULLABLE server_state, struct ifaddrs *NONNULL ifaddrs,
+                                 void *NULLABLE context);
+    DNSServiceErrorType (*NULLABLE query_record_intercept)(test_state_t *NONNULL state,
+                                                           DNSServiceRef NONNULL *NULLABLE sdRef, DNSServiceFlags flags,
+                                                           uint32_t interfaceIndex, const char *NONNULL fullname,
+                                                           uint16_t rrtype, uint16_t rrclass,
+                                                           DNSServiceAttribute const *NULLABLE attr,
+                                                           DNSServiceQueryRecordReply NONNULL callBack,
+                                                           void *NULLABLE context);
+    dns_service_query_record_callback_intercept_t NULLABLE dns_service_query_callback_intercept;
+    int variant;
+    bool test_complete;
+};
+
+#define TEST_FAIL(test_state, message)                          \
+    do {                                                        \
+        srp_test_state_explain(test_state);                     \
+        fprintf(stderr, "test failed: " message "\n\n");        \
+        exit(1);                                                \
+    } while (0)
+
+#define TEST_FAIL_CHECK(test_state, success_condition, message) \
+    do {                                                        \
+        if (!(success_condition)) {                             \
+            TEST_FAIL(test_state, message);                     \
+        }                                                       \
+    } while (0)
+
+#define TEST_FAIL_STATUS(test_state, message, status)               \
+    do {                                                            \
+           srp_test_state_explain(test_state);                      \
+           fprintf(stderr, "test failed: " message "\n\n", status); \
+           exit(1);                                                 \
+    } while (0)
+
+#define TEST_FAIL_CHECK_STATUS(test_state, success_condition, message, status) \
+    do {                                                                       \
+       if (!(success_condition)) {                                             \
+           TEST_FAIL_STATUS(test_state, message, status);                      \
+       }                                                                       \
+    } while (0)
+
+#define TEST_PASSED(test_state)         \
+    srp_test_state_explain(test_state); \
+    INFO("test passed\n");              \
+    srp_test_state_next(test_state);
+
+void srp_test_set_local_example_address(test_state_t *NONNULL state);
+void srp_test_network_localhost_start(test_state_t *NONNULL state);
+test_state_t *NULLABLE test_state_create(srp_server_t *NONNULL primary,
+                                         const char *NONNULL title, const char *NULLABLE variant_title,
+                                         const char *NONNULL explanation, const char *NULLABLE variant_name);
+void test_state_add_srp_server(test_state_t *NONNULL state, srp_server_t *NONNULL server);
+void srp_test_state_explain(test_state_t *NULLABLE state);
+void srp_test_state_next(test_state_t *NONNULL state);
+void srp_test_state_add_timeout(test_state_t *NONNULL state, int timeout);
+struct ifaddrs;
+int srp_test_getifaddrs(srp_server_t *NULLABLE server_state, struct ifaddrs *NULLABLE *NONNULL ifaddrs, void *NULLABLE context);
+void srp_test_freeifaddrs(srp_server_t *NULLABLE server_state, struct ifaddrs *NONNULL ifaddrs, void *NULLABLE context);
+void srp_test_enable_stub_router(test_state_t *NONNULL state, srp_server_t *NONNULL server_state);
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/test/test-dnssd.c b/ServiceRegistration/test/test-dnssd.c
new file mode 100644
index 0000000..d313b61
--- /dev/null
+++ b/ServiceRegistration/test/test-dnssd.c
@@ -0,0 +1,535 @@
+/* test-dnssd.c
+ *
+ * Copyright (c) 2023-2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * DNSSD intercept API for testing srp-mdns-proxy
+ */
+
+#include <dns_sd.h>
+#include "srp.h"
+#include "srp-test-runner.h"
+#include "srp-api.h"
+#include "dns-msg.h"
+#include "ioloop.h"
+#include "srp-proxy.h"
+#include "srp-dnssd.h"
+#include "srp-mdns-proxy.h"
+#include "test-api.h"
+#include "test-dnssd.h"
+
+static char *
+dns_service_rdata_to_text(test_state_t *state, int rrtype, const uint8_t *rdata, uint16_t rdlen, char *outbuf, size_t buflen)
+{
+    dns_rr_t rr;
+    unsigned offp = 0;
+
+    rr.type = rrtype;
+    TEST_FAIL_CHECK(state, dns_rdata_parse_data(&rr, rdata, &offp, rdlen, rdlen, 0), "rr parse failed");
+    dns_rdata_dump_to_buf(&rr, outbuf, buflen);
+    return outbuf;
+}
+
+static void
+dns_service_dump_event(test_state_t *state, dns_service_event_t *event, dns_service_event_t *events)
+{
+    char rrbuf[1024];
+    char *rr_printed;
+    int rrtype = 0;
+    uint16_t rdlen;
+    uint8_t *rdata;
+#define STR_OR_NULL(str) ((str) != NULL ? str : "<null>")
+    switch(event->event_type) {
+    case dns_service_event_type_register:
+        rr_printed = dns_service_rdata_to_text(state, dns_rrtype_txt, event->rdata, event->rdlen, rrbuf, sizeof(rrbuf));
+        INFO("         register: %s.%s.%s port %d IN %s (%p %p) -> %d", STR_OR_NULL(event->name),
+             STR_OR_NULL(event->regtype), STR_OR_NULL(event->domain), event->port, STR_OR_NULL(rr_printed),
+             (char *)event->sdref, (char *)event->parent_sdref, event->status);
+        break;
+    case dns_service_event_type_register_record:
+        rr_printed = dns_service_rdata_to_text(state, event->rrtype, event->rdata, event->rdlen, rrbuf, sizeof(rrbuf));
+        INFO("  register record: %s IN %s (%p %p) -> %d", STR_OR_NULL(event->name),
+             STR_OR_NULL(rr_printed), (char *)event->rref, (char *)event->sdref, event->status);
+        break;
+    case dns_service_event_type_remove_record:
+        INFO("    remove record: (%p %p) -> %d", (char *)event->rref, (char *)event->sdref, event->status);
+        break;
+    case dns_service_event_type_update_record:
+        // Get the rrtype from the previous event.
+        rdata = event->rdata;
+        rdlen = event->rdlen;
+        for (dns_service_event_t *ep = events; ep != NULL; ep = ep->next) {
+            if (ep->event_type == dns_service_event_type_register_record && ep->rref == event->rref) {
+                rrtype = ep->rrtype;
+                // When updating the TSR record, we send rdlen=0 and no rdata, which means just update the
+                // TSR and don't change the RR. But that would cause a parse failure, so we need the data
+                // as well as the rrtype from the RegisterRecord event.
+                if (rdlen == 0 && ep->rdlen != 0) {
+                    rdata = ep->rdata;
+                    rdlen = ep->rdlen;
+                }
+                break;
+            }
+        }
+        rr_printed = dns_service_rdata_to_text(state, rrtype, rdata, rdlen, rrbuf, sizeof(rrbuf));
+        INFO("    update record: %s (%p %p) -> %d", STR_OR_NULL(rr_printed), (char *)event->rref, (char *)event->sdref,
+             event->status);
+        break;
+    case dns_service_event_type_ref_deallocate:
+        INFO("   ref deallocate: (%p %p) -> %d", (char *)event->sdref, (char *)event->parent_sdref, event->status);
+        break;
+    case dns_service_event_type_register_callback:
+        INFO("     reg callback: (%p %p) -> %d", (char *)event->sdref, (char *)event->parent_sdref, event->status);
+        break;
+    case dns_service_event_type_register_record_callback:
+        INFO("  regrec callback: (%p %p) -> %d", (char *)event->rref, (char *)event->sdref, event->status);
+        break;
+    }
+}
+
+bool
+dns_service_dump_unexpected_events(test_state_t *test_state, srp_server_t *server_state)
+{
+    bool ret = true;
+    for (dns_service_event_t *event = server_state->dns_service_events; event; event = event->next) {
+        if (!event->consumed) {
+            dns_service_dump_event(test_state, event, server_state->dns_service_events);
+            ret = false;
+        }
+    }
+    return ret;
+}
+
+dns_service_event_t *
+dns_service_find_first_register_event_by_name_and_type(srp_server_t *state, const char *name,
+                                                       const char *regtype)
+{
+    for (dns_service_event_t *event = state->dns_service_events; event; event = event->next) {
+        if (event->event_type == dns_service_event_type_register &&
+            event->name != NULL && event->regtype != NULL && !event->consumed)
+        {
+            INFO("event->name %s name %s  event->regtype %s regtype %s",
+                 event->name, name, event->regtype, regtype);
+            if (!strcmp(event->name, name) && !strcmp(event->regtype, regtype)) {
+                return event;
+            }
+        }
+    }
+    return NULL;
+}
+
+dns_service_event_t *
+dns_service_find_first_register_record_event_by_name(srp_server_t *state, const char *name)
+{
+    for (dns_service_event_t *event = state->dns_service_events; event; event = event->next) {
+        if (event->event_type == dns_service_event_type_register_record &&
+            event->name != NULL && !event->consumed && !strcmp(name, event->name))
+        {
+            return event;
+        }
+    }
+    return NULL;
+}
+
+dns_service_event_t *
+dns_service_find_callback_for_registration(srp_server_t *state, dns_service_event_t *register_event)
+{
+    for (dns_service_event_t *event = state->dns_service_events; event; event = event->next) {
+        if (event->event_type == dns_service_event_type_register_callback &&
+            register_event->event_type == dns_service_event_type_register &&
+            register_event->sdref == event->sdref && !event->consumed)
+        {
+            return event;
+        }
+        if (event->event_type == dns_service_event_type_register_record_callback &&
+            register_event->event_type == dns_service_event_type_register_record &&
+            register_event->rref == event->rref && !event->consumed)
+        {
+            return event;
+        }
+    }
+    return NULL;
+}
+
+dns_service_event_t *
+dns_service_find_ref_deallocate_event(srp_server_t *state)
+{
+    for (dns_service_event_t *event = state->dns_service_events; event; event = event->next) {
+        if (event->event_type == dns_service_event_type_ref_deallocate) {
+            return event;
+        }
+    }
+    return NULL;
+}
+
+// Find a DNSServiceUpdateRecord that corresponds to a DNSServiceRegister or DNSServiceRegisterRecord event.
+// For a DNSServiceRegister update, event->rref will be NULL.
+dns_service_event_t *
+dns_service_find_update_for_register_event(srp_server_t *state, dns_service_event_t *register_event,
+                                           dns_service_event_t *after_event)
+{
+    bool after_event_matched = after_event == NULL ? true : false;
+    for (dns_service_event_t *event = state->dns_service_events; event != NULL; event = event->next) {
+        // If we are past any after_event and this is an update_record event, see if it's for the same
+        // registration.
+        if (after_event_matched && event->event_type == dns_service_event_type_update_record &&
+            ((register_event->rref == 0 && event->sdref == register_event->sdref) ||
+             (register_event->rref != 0 && event->rref == register_event->rref)))
+        {
+            return event;
+        }
+        // Don't match events that are prior to after_event (so that we can skip an event if it's not the right one
+        if (event == after_event) {
+            after_event_matched = true;
+        }
+    }
+    return NULL;
+}
+
+// Find a DNSServiceRemoveRecord
+dns_service_event_t *
+dns_service_find_remove_for_register_event(srp_server_t *state, dns_service_event_t *register_event,
+                                           dns_service_event_t *after_event)
+{
+    bool after_event_matched = after_event == NULL ? true : false;
+    for (dns_service_event_t *event = state->dns_service_events; event != NULL; event = event->next) {
+        // If we are past any after_event and this is an update_record event, see if it's for the same
+        // registration.
+        if (after_event_matched && event->event_type == dns_service_event_type_remove_record &&
+            ((register_event->rref == 0 && event->sdref == register_event->sdref) ||
+             (register_event->rref != 0 && event->rref == register_event->rref)))
+        {
+            return event;
+        }
+        // Don't match events that are prior to after_event (so that we can skip an event if it's not the right one)
+        if (event == after_event) {
+            after_event_matched = true;
+        }
+    }
+    return NULL;
+}
+
+static dns_service_ref_t *
+dns_service_ref_create(srp_server_t *server_state,
+                       DNSServiceRef *target, int flags, void *context)
+{
+    dns_service_ref_t *ret = calloc(1, sizeof(*ret));
+    TEST_FAIL_CHECK(server_state->test_state, ret != NULL, "no memory for dns_service_ref_t");
+    if (flags & kDNSServiceFlagsShareConnection) {
+        ret->sdref = *target;
+    }
+    ret->server_state = server_state;
+    ret->context = context;
+    return ret;
+}
+
+static dns_service_ref_t *
+dns_service_ref_create_for_register(srp_server_t *server_state,
+                                    DNSServiceRef *target, int flags, void *context, DNSServiceRegisterReply callback)
+{
+    dns_service_ref_t *ret = dns_service_ref_create(server_state, target, flags, context);
+    if (ret != NULL) {
+        ret->callback.register_reply = callback;
+    }
+    return ret;
+}
+
+static dns_service_ref_t *
+dns_service_ref_create_for_query_record(srp_server_t *server_state,
+                                        DNSServiceRef *target, int flags, void *context, DNSServiceQueryRecordReply callback)
+{
+    dns_service_ref_t *ret = dns_service_ref_create(server_state, target, flags, context);
+    if (ret != NULL) {
+        ret->callback.query_record_reply = callback;
+    }
+    return ret;
+}
+
+static dns_record_ref_t *
+dns_record_ref_create(srp_server_t *server_state, void *context, DNSServiceRegisterRecordReply callback)
+{
+    dns_record_ref_t *ret = calloc(1, sizeof(*ret));
+    TEST_FAIL_CHECK(server_state->test_state, ret != NULL, "no memory for dns_record_ref_t");
+    ret->server_state = server_state;
+    ret->context = context;
+    ret->callback = callback;
+    return ret;
+}
+
+static char *
+dns_service_string_dup(const char *src)
+{
+    if (src == NULL) {
+        return NULL;
+    }
+    return strdup(src);
+}
+
+static dns_service_event_t *
+dns_service_event_append(srp_server_t *state, dns_service_event_type_t event_type, DNSServiceRef sdref,
+                         DNSServiceRef in_sdref, DNSRecordRef rref, DNSServiceFlags flags, uint32_t interfaceIndex,
+                         const char *name, const char *regtype, const char *domain, const char *host, uint16_t port,
+                         uint16_t rdlen, const void *rdata, uint16_t rrtype, uint16_t rrclass, uint32_t ttl, void *attr,
+                         void *callBack, void *context, DNSServiceErrorType status)
+{
+    TEST_FAIL_CHECK(NULL, state != NULL, "invalid server state");
+    dns_service_event_t **ep = &state->dns_service_events;
+    while (*ep) {
+        ep = &(*ep)->next;
+    }
+    dns_service_event_t *event = calloc(1, sizeof(*event));
+    TEST_FAIL_CHECK(state->test_state, event != NULL, "no memory for dns service event");
+    *ep = event;
+    event->server_state = state;
+    event->event_type = event_type;
+    event->sdref = (intptr_t)sdref;
+    if (in_sdref != NULL && (flags & kDNSServiceFlagsShareConnection)) {
+        event->parent_sdref = (intptr_t)in_sdref;
+    }
+    event->rref = (intptr_t)rref;
+    event->flags = flags;
+    event->interface_index = interfaceIndex;
+    event->name = dns_service_string_dup(name);
+    event->regtype = dns_service_string_dup(regtype);
+    event->domain = dns_service_string_dup(domain);
+    event->host = dns_service_string_dup(host);
+    event->port = port;
+    event->rdlen = rdlen;
+    if (rdata != NULL) {
+        event->rdata = malloc(rdlen);
+        TEST_FAIL_CHECK(state->test_state, event->rdata != NULL, "no memory to save rdata");
+        memcpy(event->rdata, rdata, rdlen);
+    }
+    event->rrclass = rrclass;
+    event->rrtype = rrtype;
+    event->ttl = ttl;
+    event->attr = (intptr_t)attr;
+    event->callBack = (intptr_t)callBack;
+    event->context = (intptr_t)context;
+    event->status = status;
+    return event;
+}
+
+static void
+dns_service_register_callback(DNSServiceRef sdRef, DNSServiceFlags flags, DNSServiceErrorType errorCode,
+                              const char *name, const char *regtype, const char *domain, void *context)
+{
+    dns_service_ref_t *ref = context;
+    dns_service_event_append(ref->server_state, dns_service_event_type_register_callback, sdRef, NULL, NULL, flags, 0,
+                             name, regtype, domain, NULL, 0, 0, NULL, 0, 0, 0, NULL, NULL, context, errorCode);
+    DNSServiceRegisterReply callback = ref->callback.register_reply;
+    if (callback != NULL) {
+        callback(ref, flags, errorCode, name, regtype, domain, ref->context);
+    }
+}
+
+DNSServiceErrorType
+dns_service_register(srp_server_t *state, DNSServiceRef *sdRef, DNSServiceFlags flags, uint32_t interfaceIndex,
+                     const char *name, const char *regtype, const char *domain, const char *host, uint16_t port,
+                     uint16_t txtLen, const void *txtRecord, DNSServiceRegisterReply callBack, void *context)
+{
+    return dns_service_register_wa(state, sdRef, flags, interfaceIndex, name, regtype, domain, host, port, txtLen,
+                                   txtRecord, NULL, callBack, context);
+}
+
+DNSServiceErrorType
+dns_service_register_wa(srp_server_t *state, DNSServiceRef *sdRef, DNSServiceFlags flags, uint32_t interfaceIndex,
+                        const char *name, const char *regtype, const char *domain, const char *host, uint16_t port,
+                        uint16_t txtLen, const void *txtRecord, DNSServiceAttributeRef attr,
+                        DNSServiceRegisterReply callBack, void *context)
+{
+#undef DNSServiceRegisterWithAttribute
+    dns_service_ref_t *ret = dns_service_ref_create_for_register(state, sdRef, flags, context, callBack);
+    char updated_name[DNS_MAX_NAME_SIZE + 1];
+    snprintf(updated_name, sizeof(updated_name), "%d-%s", state->server_id, name);
+    int status = DNSServiceRegisterWithAttribute(&ret->sdref, flags, interfaceIndex, updated_name, regtype, domain, host, port,
+                                                 txtLen, txtRecord, attr, dns_service_register_callback, ret);
+    dns_service_event_append(state, dns_service_event_type_register, ret->sdref, *sdRef, NULL, flags,
+                             interfaceIndex, name, regtype, domain, host, port, txtLen, txtRecord,
+                             0, 0, 0, attr, callBack, context, status);
+    if (status != kDNSServiceErr_NoError) {
+        free(ret);
+    }
+    *sdRef = ret;
+    return status;
+}
+
+static void
+dns_service_register_record_callback(DNSServiceRef sdRef, DNSRecordRef RecordRef, DNSServiceFlags flags,
+                                     DNSServiceErrorType errorCode, void *context)
+{
+    dns_record_ref_t *ref = context;
+    dns_service_event_append(ref->server_state, dns_service_event_type_register_record_callback, sdRef, NULL,
+                             RecordRef, flags, 0, NULL, NULL, NULL, NULL, 0, 0, NULL, 0, 0, 0, NULL, NULL,
+                             context, errorCode);
+    if (ref->callback != NULL) {
+        ref->callback(sdRef, ref, flags, errorCode, ref->context);
+    }
+}
+
+DNSServiceErrorType
+dns_service_register_record(srp_server_t *state, DNSServiceRef sdRef, DNSRecordRef *RecordRef, DNSServiceFlags flags,
+                            uint32_t interfaceIndex, const char *fullname, uint16_t rrtype, uint16_t rrclass,
+                            uint16_t rdlen, const void *rdata, uint32_t ttl, DNSServiceRegisterRecordReply callBack,
+                            void *context)
+{
+    return dns_service_register_record_wa(state, sdRef, RecordRef, flags, interfaceIndex, fullname, rrtype, rrclass, rdlen,
+                                          rdata, ttl, NULL, callBack, context);
+}
+
+DNSServiceErrorType
+dns_service_register_record_wa(srp_server_t *state, DNSServiceRef sdRef, DNSRecordRef *RecordRef, DNSServiceFlags flags,
+                               uint32_t interfaceIndex, const char *fullname, uint16_t rrtype, uint16_t rrclass,
+                               uint16_t rdlen, const void *rdata, uint32_t ttl, DNSServiceAttributeRef attr,
+                               DNSServiceRegisterRecordReply callBack,
+                               void *context)
+{
+#undef DNSServiceRegisterRecordWithAttribute
+    dns_record_ref_t *ret = dns_record_ref_create(state, context, callBack);
+
+    // Since in testing multiple srp servers share the same mDNSResponder,
+    // we intentionally rename the record by prepending the server_id
+    // to the record name when registering with mDNSResponder, so that
+    // it will not generate conflict for replicated records.
+    char updated_name[DNS_MAX_NAME_SIZE + 1];
+    snprintf(updated_name, sizeof(updated_name), "%d-%s", state->server_id, fullname);
+    int status = DNSServiceRegisterRecordWithAttribute(sdRef, &ret->rref, flags, interfaceIndex, updated_name, rrtype,
+                                                   rrclass, rdlen, rdata, ttl, attr,
+                                                   dns_service_register_record_callback, ret);
+    dns_service_event_append(state, dns_service_event_type_register_record, sdRef, NULL, ret->rref, flags,
+                             interfaceIndex, fullname, NULL, NULL, NULL, 0, rdlen, rdata, rrtype, rrclass, ttl, attr,
+                             callBack, context, status);
+    if (status != kDNSServiceErr_NoError) {
+        free(ret);
+    }
+    *RecordRef = ret;
+    return status;
+}
+
+// Note that this will not work with a recordref returned by DNSServiceAddRecord(), which we aren't currently intercepting
+// because we don't use it.
+DNSServiceErrorType
+dns_service_remove_record(srp_server_t *state, DNSServiceRef sdRef, DNSRecordRef RecordRef, DNSServiceFlags flags)
+{
+#undef DNSServiceRemoveRecord
+    int ret = DNSServiceRemoveRecord(sdRef, RecordRef->rref, flags);
+    dns_service_event_append(state, dns_service_event_type_remove_record, sdRef->sdref, NULL, RecordRef->rref,
+                             flags, 0, NULL, NULL, NULL, NULL, 0, 0, NULL, 0, 0, 0, NULL, NULL, NULL, ret);
+    return ret;
+}
+
+DNSServiceErrorType
+dns_service_update_record(srp_server_t *state, DNSServiceRef sdRef, DNSRecordRef RecordRef, DNSServiceFlags flags,
+                          uint16_t rdlen, const void *rdata, uint32_t ttl)
+{
+    return dns_service_update_record_wa(state, sdRef, RecordRef, flags, rdlen, rdata, ttl, NULL);
+}
+
+DNSServiceErrorType
+dns_service_update_record_wa(srp_server_t *state, DNSServiceRef sdRef, DNSRecordRef RecordRef, DNSServiceFlags flags,
+                             uint16_t rdlen, const void *rdata, uint32_t ttl, DNSServiceAttributeRef attr)
+{
+#undef DNSServiceUpdateRecordWithAttribute
+    int ret;
+    if (RecordRef != NULL) {
+        ret = DNSServiceUpdateRecordWithAttribute(sdRef, RecordRef->rref, flags, rdlen, rdata, ttl, attr);
+        dns_service_event_append(state, dns_service_event_type_update_record, sdRef, NULL, RecordRef->rref, flags, 0,
+                                 NULL, NULL, NULL, NULL, 0, rdlen, rdata, 0, 0, ttl, NULL, NULL, NULL, ret);
+    } else {
+        ret = DNSServiceUpdateRecordWithAttribute(sdRef->sdref, NULL, flags, rdlen, rdata, ttl, attr);
+        dns_service_event_append(state, dns_service_event_type_update_record, sdRef->sdref, NULL, NULL, flags, 0,
+                                 NULL, NULL, NULL, NULL, 0, rdlen, rdata, 0, 0, ttl, NULL, NULL, NULL, ret);
+    }
+    return ret;
+}
+
+static void
+dns_service_query_record_callback(DNSServiceRef UNUSED sdRef, DNSServiceFlags flags, uint32_t interfaceIndex,
+                                  DNSServiceErrorType errorCode, const char *fullname, uint16_t rrtype,
+                                  uint16_t rrclass, uint16_t rdlen, const void *rdata, uint32_t ttl, void *context)
+{
+    dns_service_ref_t *ref = context;
+    srp_server_t *server_state = ref->server_state;
+    test_state_t *state = server_state->test_state;
+    bool call_callback = true;
+    dns_service_query_record_callback_intercept_t intercept = state->dns_service_query_callback_intercept;
+    if (intercept != NULL) {
+        call_callback = intercept(ref, flags, interfaceIndex, errorCode, fullname, rrtype, rrclass, rdlen, rdata, ttl, ref->context);
+    }
+    if (call_callback) {
+        DNSServiceQueryRecordReply callback = ref->callback.query_record_reply;
+        callback(ref, flags, interfaceIndex, errorCode, fullname, rrtype, rrclass, rdlen, rdata, ttl, ref->context);
+    }
+}
+
+DNSServiceErrorType
+dns_service_query_record(srp_server_t *NULLABLE srp_server, DNSServiceRef NONNULL *NULLABLE sdRef,
+                         DNSServiceFlags flags, uint32_t interfaceIndex, const char *NONNULL fullname,
+                         uint16_t rrtype, uint16_t rrclass, DNSServiceQueryRecordReply NONNULL callBack,
+                         void *NULLABLE context)
+{
+    return dns_service_query_record_wa(srp_server, sdRef, flags, interfaceIndex, fullname, rrtype, rrclass,
+                                       NULL, callBack, context);
+}
+
+DNSServiceErrorType
+dns_service_query_record_wa(srp_server_t *NULLABLE srp_server, DNSServiceRef NONNULL *NULLABLE sdRef,
+                            DNSServiceFlags flags, uint32_t interfaceIndex, const char *NONNULL fullname,
+                            uint16_t rrtype, uint16_t rrclass, DNSServiceAttribute const *NULLABLE attr,
+                            DNSServiceQueryRecordReply NONNULL callBack, void *NULLABLE context)
+{
+    dns_service_ref_t *ret = dns_service_ref_create_for_query_record(srp_server, sdRef, flags, context, callBack);
+    test_state_t *state = srp_server->test_state;
+    int status;
+    if (state->query_record_intercept != NULL) {
+        status = state->query_record_intercept(state, &ret->sdref, flags, interfaceIndex, fullname, rrtype, rrclass,
+                                               attr, dns_service_query_record_callback, ret);
+    } else {
+        status = DNSServiceQueryRecordWithAttribute(&ret->sdref, flags, interfaceIndex, fullname, rrtype, rrclass,
+                                                    attr, dns_service_query_record_callback, ret);
+    }
+    if (status != kDNSServiceErr_NoError) {
+        free(ret);
+    }
+    *sdRef = ret;
+    return status;
+}
+
+dnssd_txn_t *NULLABLE
+dns_service_ioloop_txn_add(srp_server_t UNUSED *NULLABLE srp_server, DNSServiceRef NONNULL sdref,
+                           void *NULLABLE context, dnssd_txn_finalize_callback_t NULLABLE finalize_callback,
+                           dnssd_txn_failure_callback_t NULLABLE failure_callback)
+{
+    return ioloop_dnssd_txn_add(sdref->sdref, context, finalize_callback, failure_callback);
+}
+
+void
+dns_service_ref_deallocate(srp_server_t *state, DNSServiceRef sdRef)
+{
+#undef DNSServiceRefDeallocate
+    dns_service_event_append(state, dns_service_event_type_ref_deallocate, sdRef, NULL, NULL,
+                             0, 0, NULL, NULL, NULL, NULL, 0, 0, NULL, 0, 0, 0, NULL, NULL, NULL, 0);
+    DNSServiceRefDeallocate(sdRef->sdref);
+    free(sdRef);
+}
+
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/test/test-dnssd.h b/ServiceRegistration/test/test-dnssd.h
new file mode 100644
index 0000000..b71fccc
--- /dev/null
+++ b/ServiceRegistration/test/test-dnssd.h
@@ -0,0 +1,98 @@
+/* test-dnssd.h
+ *
+ * Copyright (c) 2023 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * DNSSD intercept API for testing srp-mdns-proxy
+ */
+
+typedef struct dns_service_event dns_service_event_t;
+typedef enum {
+    dns_service_event_type_register,
+    dns_service_event_type_register_record,
+    dns_service_event_type_remove_record,
+    dns_service_event_type_update_record,
+    dns_service_event_type_ref_deallocate,
+    dns_service_event_type_register_callback,
+    dns_service_event_type_register_record_callback,
+} dns_service_event_type_t;
+
+struct dns_service_event {
+    dns_service_event_t *NULLABLE next;
+    srp_server_t *NULLABLE server_state;
+    dns_service_event_type_t event_type;
+    intptr_t sdref;
+    intptr_t parent_sdref;
+    intptr_t rref;
+    DNSServiceFlags flags;
+    uint32_t interface_index;
+    const char *NULLABLE name;
+    const char *NULLABLE regtype;
+    const char *NULLABLE domain;
+    const char *NULLABLE host;
+    uint16_t port;
+    uint16_t rrclass;
+    uint16_t rrtype;
+    uint16_t rdlen;
+    uint32_t ttl;
+    void *NULLABLE rdata;
+    intptr_t attr;
+    intptr_t callBack;
+    intptr_t context;
+    int status;
+    bool consumed;
+};
+
+typedef struct _DNSServiceRef_t dns_service_ref_t;
+struct _DNSServiceRef_t {
+    srp_server_t *NULLABLE server_state;
+    DNSServiceRef NONNULL sdref;
+    void *NULLABLE context;
+    union {
+        DNSServiceRegisterReply NONNULL register_reply;
+        DNSServiceQueryRecordReply NONNULL query_record_reply;
+    } callback;
+};
+
+typedef struct _DNSRecordRef_t dns_record_ref_t;
+struct _DNSRecordRef_t {
+    srp_server_t *NULLABLE server_state;
+    DNSRecordRef NONNULL rref;
+    void *NULLABLE context;
+    DNSServiceRegisterRecordReply NULLABLE callback;
+};
+
+bool dns_service_dump_unexpected_events(test_state_t *NONNULL test_state, srp_server_t *NONNULL server_state);
+dns_service_event_t *NULLABLE dns_service_find_first_register_event_by_name_and_type(srp_server_t *NONNULL state,
+                                                                                     const char *NONNULL name,
+                                                                                     const char *NONNULL regtype);
+dns_service_event_t *NULLABLE dns_service_find_first_register_record_event_by_name(srp_server_t *NONNULL state,
+                                                                                   const char *NONNULL name);
+dns_service_event_t *NULLABLE dns_service_find_callback_for_registration(srp_server_t *NONNULL state,
+                                                                         dns_service_event_t *NONNULL register_event);
+dns_service_event_t *NULLABLE dns_service_find_ref_deallocate_event(srp_server_t *NONNULL state);
+dns_service_event_t *NULLABLE dns_service_find_update_for_register_event(srp_server_t *NONNULL state,
+                                                                         dns_service_event_t *NONNULL register_event,
+                                                                         dns_service_event_t *NULLABLE after_event);
+dns_service_event_t *NULLABLE dns_service_find_remove_for_register_event(srp_server_t *NONNULL state,
+                                                                         dns_service_event_t *NONNULL register_event,
+                                                                         dns_service_event_t *NULLABLE after_event);
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/test/test-packet.c b/ServiceRegistration/test/test-packet.c
new file mode 100644
index 0000000..2fab4b5
--- /dev/null
+++ b/ServiceRegistration/test/test-packet.c
@@ -0,0 +1,166 @@
+/* test-packet.c
+ *
+ * Copyright (c) 2023 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains tools for generating SRP update packets that can be fed directly into the
+ * parser (srp-parse.c) rather than involving connections, using the pathway that's used by SRP
+ * replication. This is useful for unit tests generally, and particularly for testing the multi-packet
+ * functionality used by SRP replication.
+ */
+
+#include <dns_sd.h>
+#include "srp.h"
+#include "srp-test-runner.h"
+#include "srp-api.h"
+#include "dns-msg.h"
+#include "ioloop.h"
+#include "srp-proxy.h"
+#include "srp-dnssd.h"
+#include "srp-mdns-proxy.h"
+#include "test-api.h"
+#include "srp-replication.h"
+#include "test-packet.h"
+#include "test-dnssd.h"
+#include "dnssd-proxy.h"
+#include "srp-tls.h"
+#include <arpa/inet.h>
+
+#define MAX_PACKETS 10
+typedef struct test_packet_state test_packet_state_t;
+struct test_packet_state {
+    test_state_t *test_state;
+    client_state_t *current_client;
+    srpl_connection_t *srpl_connection;
+    message_t *packets[MAX_PACKETS];
+    int num_packets;
+};
+
+test_packet_state_t *
+test_packet_state_create(test_state_t *state, void (*advertise_finished_callback)(test_state_t *state))
+{
+    test_packet_state_t *packet_state = calloc(1, sizeof(*packet_state));
+    TEST_FAIL_CHECK(state, state != NULL, "unable to allocate test packet state");
+    packet_state->test_state = state;
+    srp_host_init(state);
+    packet_state->current_client = srp_client_get_current();
+
+    // We need to add at least one address record.
+    srp_test_set_local_example_address(state);
+
+    // We need an SRPL connection to capture the advertise_finished event. It needs to look real enough that
+    // the event gets delivered, so we have to create a domain and an instance and tie them together, and then
+    // hang the connection off of the instance.
+    srpl_instance_t *instance = calloc(1, sizeof (*instance));
+    TEST_FAIL_CHECK(state, instance != NULL, "no memory for instance");
+    instance->instance_name = strdup("single-srpl-instance");
+    TEST_FAIL_CHECK(state, instance->instance_name != NULL, "no memory for instance name");
+    instance->domain = srpl_domain_create_or_copy(state->primary, "openthread.thread.home.arpa");
+    instance->domain->srpl_opstate = SRPL_OPSTATE_ROUTINE;
+    TEST_FAIL_CHECK(state, instance->domain != NULL, "no domain created");
+    instance->domain->instances = instance;
+    RETAIN_HERE(instance->domain, srpl_domain);
+    RETAIN_HERE(instance->domain->instances, srpl_instance);
+
+    srpl_connection_t *srpl_connection = srpl_connection_create(instance, false);
+    TEST_FAIL_CHECK(state, srpl_connection != NULL, "srpl_connection_create failed");
+    srpl_connection->state = srpl_state_test_event_intercept;
+    instance->connection = srpl_connection;
+    RETAIN_HERE(instance->connection, srpl_connection);
+
+    srpl_connection->test_state = state;
+    srpl_connection->advertise_finished_callback = advertise_finished_callback;
+    packet_state->srpl_connection = srpl_connection;
+    RETAIN_HERE(packet_state->srpl_connection, srpl_connection);
+
+    return packet_state;
+}
+
+void
+test_packet_generate(test_state_t *state, uint32_t host_lease, uint32_t key_lease, bool removing, bool prepend)
+{
+    test_packet_state_t *packet_state = state->test_packet_state;
+    message_t *message = ioloop_message_create(sizeof(dns_wire_t));
+    size_t length = message->length;
+    message->received_time = srp_time();
+    message->lease = host_lease;
+    message->key_lease = key_lease;
+    dns_wire_t *ret = srp_client_generate_update(packet_state->current_client, host_lease, key_lease,
+                                                 &length, &message->wire, 9999, removing);
+    TEST_FAIL_CHECK(state, length <= message->length, "srp_client_generate overflowed message length");
+    TEST_FAIL_CHECK(state, ret != NULL, "srp_client_generate returned NULL");
+    TEST_FAIL_CHECK(state, packet_state->num_packets < MAX_PACKETS, "more than maximum number of packets");
+    if (prepend) {
+        for (int i = packet_state->num_packets; i > 0; i--) {
+            packet_state->packets[i] = packet_state->packets[i - 1];
+        }
+        packet_state->packets[0] = message;
+    } else {
+        packet_state->packets[packet_state->num_packets] = message;
+    }
+    packet_state->num_packets++;
+}
+
+void
+test_packet_reset_key(test_state_t *state)
+{
+    test_packet_state_t *packet_state = state->test_packet_state;
+    srp_host_key_reset_for_client(packet_state->current_client);
+}
+
+void
+test_packet_start(test_state_t *state, bool expect_fail)
+{
+    test_packet_state_t *packet_state = state->test_packet_state;
+    bool success = srp_parse_host_messages_evaluate(state->primary, packet_state->srpl_connection,
+                                                    packet_state->packets, packet_state->num_packets);
+    TEST_FAIL_CHECK_STATUS(state, expect_fail != success, "srp_parse_host_messages_evaluate returned " PUB_S_SRP,
+                           success ? "true" : "false");
+    if (expect_fail) {
+        TEST_PASSED(state);
+    }
+}
+
+bool
+test_packet_srpl_intercept(srpl_connection_t *srpl_connection, srpl_event_t *event)
+{
+    if (event->event_type == srpl_event_advertise_finished) {
+        test_state_t *state = srpl_connection->test_state;
+        srpl_connection->advertise_finished_callback(state);
+    }
+    return false;
+}
+
+void
+test_packet_message_delete(test_state_t *test_state, int index)
+{
+    test_packet_state_t *state = test_state->test_packet_state;
+    if (index < state->num_packets) {
+        ioloop_message_release(state->packets[index]);
+        int j = index;
+        for (int i = index + 1; i < state->num_packets; i++) {
+            state->packets[j++] = state->packets[i];
+        }
+        state->num_packets--;
+    }
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/test/test-packet.h b/ServiceRegistration/test/test-packet.h
new file mode 100644
index 0000000..7a4dbda
--- /dev/null
+++ b/ServiceRegistration/test/test-packet.h
@@ -0,0 +1,37 @@
+/* test-packet.c
+ *
+ * Copyright (c) 2023 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains external definitions for test-packet.c.
+ */
+
+test_packet_state_t *NULLABLE
+test_packet_state_create(test_state_t *NONNULL test_state,
+                         void (*NONNULL advertise_finished_callback)(test_state_t *NONNULL test_state));
+void test_packet_generate(test_state_t *NONNULL test_state, uint32_t lease_time, uint32_t key_lease_time,
+                          bool removing, bool prepend);
+void test_packet_reset_key(test_state_t *NONNULL test_state);
+void test_packet_start(test_state_t *NONNULL state, bool expect_fail);
+bool test_packet_srpl_intercept(srpl_connection_t *NONNULL srpl_connection, srpl_event_t *NULLABLE event);
+void test_packet_message_delete(test_state_t *NONNULL state, int index);
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/test/test-srpl.c b/ServiceRegistration/test/test-srpl.c
new file mode 100644
index 0000000..5b2b536
--- /dev/null
+++ b/ServiceRegistration/test/test-srpl.c
@@ -0,0 +1,215 @@
+/* test-srpl.c
+ *
+ * Copyright (c) 2023-2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains tools for SRP replication.
+ */
+
+#include <dns_sd.h>
+#include "srp.h"
+#include "srp-test-runner.h"
+#include "srp-api.h"
+#include "dns-msg.h"
+#include "ioloop.h"
+#include "srp-proxy.h"
+#include "srp-dnssd.h"
+#include "srp-mdns-proxy.h"
+#include "test-api.h"
+#include "srp-replication.h"
+#include "test-packet.h"
+#include "test-dnssd.h"
+#include "dnssd-proxy.h"
+#include "test-srpl.h"
+#include "srp-tls.h"
+#include <arpa/inet.h>
+
+#define MAX_PACKETS 10
+typedef struct test_packet_state test_packet_state_t;
+struct test_packet_state {
+    test_state_t *test_state;
+    client_state_t *current_client;
+    srpl_connection_t *srpl_connection;
+    message_t *packets[MAX_PACKETS];
+    int num_packets;
+};
+
+static bool tls_init = false;
+
+static void
+test_srpl_input(comm_t *comm, message_t *message, void *context)
+{
+    srp_server_t *server_state = context;
+    dns_proxy_input_for_server(comm, server_state, message, NULL);
+}
+
+static void
+test_srpl_listener_ready(void *context, uint16_t port)
+{
+    srp_server_t *server = context;
+    srpl_connection_t *srpl_connection;
+    srpl_instance_service_t *service;
+    address_query_t *address_query;
+
+    // listener is ready, so we start to connect on the outgoing connections
+    for (srpl_connection = server->connections; srpl_connection != NULL; srpl_connection = srpl_connection->next)
+    {
+        srpl_instance_t *instance = srpl_connection->instance;
+        service = calloc(1, sizeof(*service));
+        RETAIN_HERE(service, srpl_instance_service);
+        service->outgoing_port = port;
+        address_query = calloc(1, sizeof(*address_query));
+        RETAIN_HERE(address_query, address_query);
+        inet_pton(AF_INET, "127.0.0.1", &address_query->addresses[0].sin.sin_addr);
+        address_query->addresses[0].sa.sa_family = AF_INET;
+        address_query->num_addresses = 1;
+        address_query->cur_address = -1;
+        service->address_query = address_query;
+        service->instance = instance;
+        RETAIN_HERE(instance, srpl_instance);
+        service->domain = instance->domain;
+        RETAIN_HERE(service->domain, srpl_domain);
+        instance->services = service;
+        srpl_connection_next_state(srpl_connection, srpl_state_next_address_get);
+        address_query->cur_address = -1;
+    }
+}
+
+static void
+test_srpl_connection_connected(comm_t *connection, void *context)
+{
+    srp_server_t *server = context;
+    connection->srp_server = server;
+}
+
+srp_server_t *
+test_srpl_add_server(test_state_t *state)
+{
+    static int server_id = 1;
+    srp_server_t *server = server_state_create("srp-mdns-proxy-2",
+                                               3600 * 27,     // max lease time one day plus 20%
+                                               30,            // min lease time 30 seconds
+                                               3600 * 24 * 7, // max key lease 7 days
+                                               30);           // min key lease time 30s
+    server->advertise_interface = if_nametoindex("lo0"); // Test is local to the device.
+    server->test_state = state;
+    server->srp_replication_enabled = true;
+    srp_test_enable_stub_router(state, server);
+    server->server_id = server_id;
+    server_id++;
+    return server;
+}
+
+static srpl_instance_t*
+test_srpl_instance_create(test_state_t *state, srp_server_t *server)
+{
+    srpl_instance_t *instance = calloc(1, sizeof (*instance));
+    TEST_FAIL_CHECK(state, instance != NULL, "no memory for instance");
+    instance->instance_name = strdup("single-srpl-instance");
+    TEST_FAIL_CHECK(state, instance->instance_name != NULL, "no memory for instance name");
+    instance->domain = srpl_domain_create_or_copy(server, "openthread.thread.home.arpa");
+    TEST_FAIL_CHECK(state, instance->domain != NULL, "no domain created");
+    instance->domain->srpl_opstate = SRPL_OPSTATE_ROUTINE;
+    instance->have_partner_id = true;
+    server->current_thread_domain_name = strdup(instance->domain->name);
+    instance->domain->instances = instance;
+    RETAIN_HERE(instance->domain, srpl_domain);
+    RETAIN_HERE(instance->domain->instances, srpl_instance);
+    return instance;
+}
+
+static srpl_connection_t *
+test_srpl_instance_connection_create(test_state_t *state, srp_server_t *server)
+{
+    srpl_instance_t *instance = test_srpl_instance_create(state, server);
+    TEST_FAIL_CHECK(state, instance != NULL, "no memory for instance");
+
+    srpl_connection_t *srpl_connection = srpl_connection_create(instance, true);
+    TEST_FAIL_CHECK(state, srpl_connection != NULL, "srpl_connection_create failed");
+    srpl_connection->state = srpl_state_connecting;
+    instance->connection = srpl_connection;
+    RETAIN_HERE(instance->connection, srpl_connection);
+
+    srpl_connection->test_state = state;
+    return srpl_connection;
+}
+
+// create outgoing connection from client to server. It'll connect once the listener
+// on the server side is ready.
+srpl_connection_t *
+test_srpl_connection_create(test_state_t *state, srp_server_t *server, srp_server_t *client)
+{
+    srpl_connection_t *connection = test_srpl_instance_connection_create(state, client);
+    connection->next = server->connections;
+    connection->server = server;
+    server->connections = connection;
+    return connection;
+}
+
+// server starts listener and gets ready for srpl connection
+void
+test_srpl_start_replication(srp_server_t *server, int16_t port)
+{
+    test_state_t *state = server->test_state;
+    // create a domain, and an instance and tie them together. Also create a address_query so that
+    // the incoming connection can be recognized.
+    srpl_instance_t *instance = test_srpl_instance_create(state, server);
+    TEST_FAIL_CHECK(state, instance != NULL, "no memory for instance");
+    srpl_instance_service_t *service = calloc(1, sizeof (*service));
+    TEST_FAIL_CHECK(state, service != NULL, "no memory for service");
+    service->instance = instance;
+    RETAIN_HERE(instance, srpl_instance);
+    instance->services = service;
+    RETAIN_HERE(service, srpl_instance_service);
+
+    // create an address_query for local loopback address so that the incoming
+    // connection can be recognized.
+    addr_t addr;
+    memset(&addr, 0, sizeof(addr));
+    inet_pton(AF_INET, "127.0.0.1", &addr.sin.sin_addr);
+    addr.sa.sa_family = AF_INET;
+    addr.sin.sin_port = htons(port);
+    addr.sa.sa_len = sizeof(addr.sin);
+    address_query_t *address_query = calloc(1, sizeof (*address_query));
+    TEST_FAIL_CHECK(state, address_query != NULL, "no memory for address query");
+    address_query->num_addresses = 1;
+    address_query->cur_address = 0;
+    memcpy(&address_query->addresses[0], &addr, sizeof(addr));
+    service->address_query = address_query;
+    RETAIN_HERE(address_query, address_query);
+    instance->domain->srpl_opstate = SRPL_OPSTATE_ROUTINE;
+    // we intentionaly pick the smallest partner id so that it would not reject
+    // any incoming connection.
+    instance->domain->partner_id = 0;
+
+    if (!tls_init) {
+        bool succeeded = srp_tls_init();
+        TEST_FAIL_CHECK(state, succeeded, "srp_tls_init failed");
+        tls_init = true;
+    }
+    server->srpl_listener = ioloop_listener_create(true, true, false, NULL, 0, &addr, NULL, "SRPL Listener",
+                                                   test_srpl_input, test_srpl_connection_connected, NULL,
+                                                   test_srpl_listener_ready, NULL, srp_tls_configure, 0, server);
+    TEST_FAIL_CHECK(state, server->srpl_listener != NULL, "no memory for listener");
+    server->srpl_listener->srp_server = server;
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/test/test-srpl.h b/ServiceRegistration/test/test-srpl.h
new file mode 100644
index 0000000..0b90291
--- /dev/null
+++ b/ServiceRegistration/test/test-srpl.h
@@ -0,0 +1,34 @@
+/* test-srpl.h
+ *
+ * Copyright (c) 2023 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains external definitions for test-srpl.c.
+ */
+
+srp_server_t *NULLABLE test_srpl_add_server(test_state_t *NONNULL state);
+void test_srpl_start_replication(srp_server_t *NONNULL server, int16_t port);
+srpl_connection_t *NULLABLE test_srpl_connection_create(test_state_t *NONNULL state, srp_server_t *NONNULL server, srp_server_t *NONNULL client);
+void test_srpl_finished_evaluate(srpl_connection_t *NONNULL srpl_connection);
+void test_srpl_set_finished_checkpoint(srpl_connection_t *NONNULL srpl_connection,
+                                       srpl_state_t srpl_state,
+                                       void (*NULLABLE test_finished_callback)(test_state_t *NONNULL state, srp_server_t *NONNULL server));
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/test/tests/change-text-record.c b/ServiceRegistration/test/tests/change-text-record.c
new file mode 100644
index 0000000..40b5151
--- /dev/null
+++ b/ServiceRegistration/test/tests/change-text-record.c
@@ -0,0 +1,161 @@
+/* change-text-record.c
+ *
+ * Copyright (c) 2023-2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains the SRP server test runner.
+ */
+
+#include "srp.h"
+#include <dns_sd.h>
+#include "srp-test-runner.h"
+#include "srp-api.h"
+#include "dns-msg.h"
+#include "ioloop.h"
+#include "srp-mdns-proxy.h"
+#include "test-api.h"
+#include "srp-proxy.h"
+#include "srp-mdns-proxy.h"
+#include "test-dnssd.h"
+#include "test.h"
+
+static void
+test_change_text_record_test_evaluate(test_state_t *state)
+{
+    dns_service_event_t *register_event = dns_service_find_first_register_event_by_name_and_type(state->primary,
+                                                                                                 TEST_INSTANCE_NAME,
+                                                                                                 TEST_SERVICE_TYPE);
+    TEST_FAIL_CHECK(state, register_event != NULL, "failed to initially register service");
+    dns_service_event_t *update_event = dns_service_find_update_for_register_event(state->primary,
+                                                                                   register_event, NULL);
+    TEST_FAIL_CHECK(state, update_event != NULL, "failed to correctly update service");
+    // An update event with a NULL rdata is a TSR update, which isn't what we're looking for.
+    while (update_event != NULL && update_event->rdata == NULL) {
+        INFO("skipping TSR update");
+        update_event = dns_service_find_update_for_register_event(state->primary, register_event, update_event);
+    }
+    TEST_FAIL_CHECK(state, update_event != NULL, "failed to correctly update service");
+    TEST_PASSED(state);
+}
+
+static void
+test_change_text_record_callback(DNSServiceRef sdref, DNSServiceFlags UNUSED flags, DNSServiceErrorType errorCode,
+                                 const char *name, const char *regtype, const char *UNUSED domain, void *context)
+{
+    srp_server_t *server_state = context;
+    test_state_t *state = server_state->test_state;
+    static bool updated_text_record = false;
+
+    INFO("Register Reply for %s . %s: %d", name, regtype, errorCode);
+    INFO("state = %p", state);
+
+    if (errorCode != kDNSServiceErr_NoError) {
+        if (updated_text_record) {
+            TEST_FAIL_STATUS(state, "text record update failed: srp_client_register callback returned %d", errorCode);
+        } else {
+            TEST_FAIL_STATUS(state, "initial registration failed: srp_client_register callback returned %d", errorCode);
+        }
+    }
+
+    // if we have already updated text record, there's nothing to do;
+    // otherwise, schedule a txt update.
+    if (updated_text_record) {
+        return;
+    }
+
+    // Allow time for the mDNS registration to finish
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 2), dispatch_get_main_queue(), ^{
+            char txt_buf[128];
+            TXTRecordRef txt;
+
+            TXTRecordCreate(&txt, sizeof(txt_buf), txt_buf);
+            TXTRecordSetValue(&txt, "foo", 1, "1");
+            TXTRecordSetValue(&txt, "bar", 3, "2.1");
+            const char *txt_data = TXTRecordGetBytesPtr(&txt);
+            int txt_len = TXTRecordGetLength(&txt);
+
+            int ret = srp_client_update_record(sdref, NULL, 0, txt_len, txt_data, 0);
+            TEST_FAIL_CHECK_STATUS(state, ret == kDNSServiceErr_NoError,
+                                   "text record update failed: srp_client_update_record returned %d", ret);
+            srp_network_state_stable(NULL);
+            updated_text_record = true;
+
+            // We will not get another callback because of a TXT record update unless it produces a conflict.
+            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 2), dispatch_get_main_queue(), ^{
+                    srp_client_ref_deallocate(sdref);
+                    srp_network_state_stable(NULL); // Discontinue SRP client
+                    // Allow any cleanup to happen and then evaluate results.
+                    dispatch_async(dispatch_get_main_queue(), ^{
+                            test_change_text_record_test_evaluate(state);
+                        });
+                });
+        });
+}
+
+static void
+test_change_text_record_ready(void *context, uint16_t UNUSED port)
+{
+    srp_server_t *state = context;
+
+    int ret = srp_host_init(state);
+    TEST_FAIL_CHECK(state->test_state, ret == kDNSServiceErr_NoError, "srp_host_init failed");
+
+    DNSServiceRef ref;
+    INFO("Change text record test");
+    char txt_buf[128];
+    TXTRecordRef txt;
+
+    TXTRecordCreate(&txt, sizeof(txt_buf), txt_buf);
+    TXTRecordSetValue(&txt, "foo", 1, "1");
+    TXTRecordSetValue(&txt, "bar", 3, "1.1");
+    const char *txt_data = TXTRecordGetBytesPtr(&txt);
+    int txt_len = TXTRecordGetLength(&txt);
+
+    // Create a DNSSD client
+    ret = srp_client_register(&ref, 0 /* flags */, 0 /* interfaceIndex */,
+                              TEST_INSTANCE_NAME /* name */, TEST_SERVICE_TYPE /* regType */,
+                              NULL /* domain */, TEST_HOST_NAME /* host */, 1234, txt_len, txt_data,
+                              test_change_text_record_callback, state);
+    TEST_FAIL_CHECK_STATUS(state->test_state, ret == kDNSServiceErr_NoError, "srp_client_register returned %d", ret);
+    srp_test_network_localhost_start(state->test_state);
+}
+
+void
+test_change_text_record_start(test_state_t *next_test)
+{
+    extern srp_server_t *srp_servers;
+    const char *description =
+        "  The goal of this test is to see that when we add a service with SRP and then update the\n"
+        "  service to change its text record, srp-mdns-proxy calls DNSServiceUpdateRecord rather than\n"
+        "  calling DNSServiceRefDeallocate followed by DNSServiceRegister.";
+    test_state_t *state = test_state_create(srp_servers, "Change Text Record test", NULL, description, NULL);
+
+    srp_proxy_init("local");
+    srp_test_enable_stub_router(state, srp_servers);
+    state->srp_listener = srp_proxy_listen(NULL, 0, NULL, test_change_text_record_ready, NULL, NULL, NULL, state->primary);
+    TEST_FAIL_CHECK(state, state->srp_listener != NULL, "listener create failed");
+    state->next = next_test;
+
+    // Test should not take longer than ten seconds.
+    srp_test_state_add_timeout(state, 10);
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/test/tests/dangling-query.c b/ServiceRegistration/test/tests/dangling-query.c
new file mode 100644
index 0000000..9604b52
--- /dev/null
+++ b/ServiceRegistration/test/tests/dangling-query.c
@@ -0,0 +1,147 @@
+/* dangling-query.c
+ *
+ * Copyright (c) 2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains the SRP server test runner.
+ */
+
+#include "srp.h"
+#include <dns_sd.h>
+#include "srp-test-runner.h"
+#include "srp-api.h"
+#include "dns-msg.h"
+#include "ioloop.h"
+#include "srp-mdns-proxy.h"
+#include "test-api.h"
+#include "srp-proxy.h"
+#include "srp-mdns-proxy.h"
+#include "test-dnssd.h"
+#include "test.h"
+#include "dnssd-proxy.h"
+
+static bool
+test_dns_dangling_query_response_intercept(comm_t *UNUSED connection, message_t *UNUSED responding_to,
+                                           struct iovec *UNUSED iov, int UNUSED iov_len, bool UNUSED final,
+                                           bool UNUSED send_length)
+{
+    test_state_t *state = connection->test_context;
+    if (state->counter == 0) {
+        state->counter = 1;
+        TEST_FAIL_CHECK(state, state->context != NULL && state->context != last_freed_domain,
+                        "domain was freed after the first query finished.");
+        return true;
+    }
+    dispatch_async(dispatch_get_main_queue(), ^{
+            TEST_FAIL_CHECK(state, state->context != NULL && state->context == last_freed_domain,
+                            "domain wasn't freed last.");
+            TEST_PASSED(state);
+        });
+    return true;
+}
+
+static void
+test_dns_dangling_query_ready(void *context, uint16_t UNUSED port)
+{
+    test_state_t *state = context;
+
+    // Hook our callback in to the srp input connection.
+    state->srp_listener->test_send_intercept = test_dns_dangling_query_response_intercept;
+    state->srp_listener->test_context = state;
+
+    // Generate a DNS query that's in the infrastructure pseudo interface domain.
+    message_t *message = ioloop_message_create(DNS_HEADER_SIZE + 256); // should be plenty of room
+    TEST_FAIL_CHECK(state, message != NULL, "no memory for DNS query");
+    dns_wire_t *wire = &message->wire;
+    dns_towire_state_t towire;
+    memset(&towire, 0, sizeof(towire));
+
+    towire.p = &wire->data[0];               // We start storing RR data here.
+    towire.lim = &wire->data[DNS_DATA_SIZE]; // This is the limit to how much we can store.
+    towire.message = wire;
+
+    wire->id = srp_random16();
+    wire->bitfield = 0;
+    dns_qr_set(wire, dns_qr_query);
+    dns_opcode_set(wire, dns_opcode_query);
+    wire->qdcount = htons(1);
+
+    // A browse query that probably will get at least one answer
+    dns_full_name_to_wire(NULL, &towire, "_airplay._tcp.local.");
+    dns_u16_to_wire(&towire, dns_rrtype_ptr);
+    dns_u16_to_wire(&towire, dns_qclass_in);
+    // No ttl or length or rdata because question
+
+    dns_proxy_input_for_server(state->srp_listener, state->primary, message, NULL);
+
+    // second query
+    towire.p = &wire->data[0];               // We start storing RR data here.
+    towire.lim = &wire->data[DNS_DATA_SIZE]; // This is the limit to how much we can store.
+    towire.message = wire;
+
+    wire->id = srp_random16();
+    wire->bitfield = 0;
+    // A browse query that probably will get at least one answer
+    dns_full_name_to_wire(NULL, &towire, "_companion-link._tcp.local.");
+    dns_u16_to_wire(&towire, dns_rrtype_ptr);
+    dns_u16_to_wire(&towire, dns_qclass_in);
+    // No ttl or length or rdata because question
+
+    dns_proxy_input_for_server(state->srp_listener, state->primary, message, NULL);
+
+    state->context = delete_served_domain_by_interface_name(INFRASTRUCTURE_PSEUDO_INTERFACE);
+    TEST_FAIL_CHECK(state, state->context != NULL && state->context != last_freed_domain,
+                    "domain was freed immediately (way too soon).");
+}
+
+static bool
+test_listen_dangling_dnssd_proxy_configure(void)
+{
+    dnssd_proxy_udp_port= 5300;
+    dnssd_proxy_tcp_port = 5300;
+    dnssd_proxy_tls_port = 8530;
+    return true;
+}
+
+void
+test_dns_dangling_query(test_state_t *next_test)
+{
+    extern srp_server_t *srp_servers;
+    const char *description =
+        "  The goal of this test is to create an interface-specific served domain, start two queries in that\n"
+		"  domain, then delete the domain, then wait for the query to finish. This should not (obvsly)\n"
+        "  crash. In addition, when the queries finish, the test domain should be freed.";
+    test_state_t *state = test_state_create(srp_servers, "Dangling Query test", NULL, description, NULL);
+    state->next = next_test;
+
+    srp_proxy_init("local");
+    srp_test_enable_stub_router(state, srp_servers);
+    state->dnssd_proxy_configurer = test_listen_dangling_dnssd_proxy_configure;
+    TEST_FAIL_CHECK(state, init_dnssd_proxy(srp_servers), "failed to setup dnssd-proxy");
+
+    // Start the srp listener.
+    state->srp_listener = srp_proxy_listen(NULL, 0, NULL, test_dns_dangling_query_ready, NULL, NULL, NULL, state);
+
+    // Test should not take longer than ten seconds.
+    srp_test_state_add_timeout(state, 10);
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/test/tests/dns-push.c b/ServiceRegistration/test/tests/dns-push.c
new file mode 100644
index 0000000..5864c43
--- /dev/null
+++ b/ServiceRegistration/test/tests/dns-push.c
@@ -0,0 +1,934 @@
+/* dns-push.c
+ *
+ * Copyright (c) 2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains the SRP server test runner.
+ */
+
+#include "srp.h"
+#include <dns_sd.h>
+#include <arpa/inet.h>
+#include "srp-test-runner.h"
+#include "srp-api.h"
+#include "dns-msg.h"
+#include "ioloop.h"
+#include "srp-mdns-proxy.h"
+#include "test-api.h"
+#include "srp-proxy.h"
+#include "srp-mdns-proxy.h"
+#include "test-dnssd.h"
+#include "test.h"
+#include "dnssd-proxy.h"
+#define DNSMessageHeader dns_wire_t
+#include "dso.h"
+#include "dso-utils.h"
+
+#define SUBSCRIBE_LIMIT 16 // SOA + SRV + A + AAAA || PTR + SRV + A + AAAA
+#define TXN_LIMIT SUBSCRIBE_LIMIT * 4
+typedef struct push_test_state push_test_state_t;
+struct push_test_state {
+    test_state_t *test_state;
+    comm_t *dso_connection;
+    wakeup_t *wait_for_remote_disconnect;
+    dso_state_t *disconnect_expected;
+    DNSServiceRef register_ref, ptr_sdref;
+    DNSServiceRef txns[TXN_LIMIT];
+    int num_txns;
+    uint16_t subscribe_xids[SUBSCRIBE_LIMIT];
+    int num_subscribe_xids, soa_index, ds_index[2];
+    char *hostname;
+    char *srv_name;
+    int num_service_adds_pre, num_service_removes, num_service_adds_post;
+    int num_address_adds_pre, num_address_removes, num_address_adds_post;
+    uint16_t keepalive_xid;
+    int variant, num_a_records, num_aaaa_records;
+    int num_txt_records, num_srv_records;
+    bool push_send_bogus_keepalive, push_unsubscribe;
+    bool push_subscribe_sent, have_address_records;
+    bool server_was_crashed, server_is_being_crashed;
+    bool test_dns_push, have_keepalive_response, need_service;
+};
+
+static void test_dns_push_send_push_subscribe(push_test_state_t *push_state, const char *name, int rrtype);
+
+static void
+test_dns_push_dso_message_finished(void *context, message_t *UNUSED message, dso_state_t *dso)
+{
+    push_test_state_t *push_state = context;
+
+    if (dso->primary.opcode == kDSOType_DNSPushUnsubscribe) {
+        if (dso->activities == NULL) {
+            dispatch_async(dispatch_get_main_queue(), ^{
+                    TEST_PASSED(push_state->test_state);
+                });
+        }
+    }
+}
+
+static void
+test_dns_push_send_push_unsubscribe(push_test_state_t *push_state, int index)
+{
+    if (push_state->subscribe_xids[index] != 0) {
+        struct iovec iov;
+        dns_wire_t dns_message;
+        uint8_t *buffer = (uint8_t *)&dns_message;
+        dns_towire_state_t towire;
+        dso_message_t message;
+
+        INFO("unsubscribe %x %d", push_state->subscribe_xids[index], index);
+        dso_make_message(&message, buffer, sizeof(dns_message), push_state->dso_connection->dso, true, false, 0, 0, NULL);
+        memset(&towire, 0, sizeof(towire));
+        towire.p = &buffer[DNS_HEADER_SIZE];
+        towire.lim = towire.p + (sizeof(dns_message) - DNS_HEADER_SIZE);
+        towire.message = &dns_message;
+        dns_u16_to_wire(&towire, kDSOType_DNSPushUnsubscribe);
+        dns_rdlength_begin(&towire);
+        dns_u16_to_wire(&towire, push_state->subscribe_xids[index]);
+        dns_rdlength_end(&towire);
+
+        memset(&iov, 0, sizeof(iov));
+        iov.iov_len = towire.p - buffer;
+        iov.iov_base = buffer;
+        ioloop_send_message(push_state->dso_connection, NULL, &iov, 1);
+        push_state->subscribe_xids[index] = 0; // Don't unsubscribe again.
+    }
+}
+
+static void
+test_dns_push_unsubscribe_all(push_test_state_t *push_state)
+{
+    struct iovec iov;
+    INFO("unsubscribe");
+    dns_wire_t dns_message;
+    uint8_t *buffer = (uint8_t *)&dns_message;
+    dns_towire_state_t towire;
+    dso_message_t message;
+    if (!push_state->push_send_bogus_keepalive) {
+        for (int i = 0; i < push_state->num_subscribe_xids; i++) {
+            test_dns_push_send_push_unsubscribe(push_state, i);
+        }
+    }
+
+    // Send a keepalive message so that we can get the response, since the unsubscribe is not a response-requiring request.
+    dso_make_message(&message, buffer, sizeof(dns_message), push_state->dso_connection->dso, false, false, 0, 0, NULL);
+    memset(&towire, 0, sizeof(towire));
+    towire.p = &buffer[DNS_HEADER_SIZE];
+    towire.lim = towire.p + (sizeof(dns_message) - DNS_HEADER_SIZE);
+    towire.message = &dns_message;
+    dns_u16_to_wire(&towire, kDSOType_Keepalive);
+    dns_rdlength_begin(&towire);
+    dns_u32_to_wire(&towire, 600);
+    dns_u32_to_wire(&towire, 600);
+    dns_rdlength_end(&towire);
+    if (push_state->push_send_bogus_keepalive) {
+        INFO("sending bogus keepalive");
+        // Send a badly formatted message.
+        dns_u32_to_wire(&towire, 0x12345678);
+    }
+    push_state->keepalive_xid = dns_message.id;
+    memset(&iov, 0, sizeof(iov));
+    iov.iov_len = towire.p - buffer;
+    iov.iov_base = buffer;
+    ioloop_send_message(push_state->dso_connection, NULL, &iov, 1);
+}
+
+static void
+test_dns_push_remote_disconnect_didnt_happen(void *context)
+{
+    push_test_state_t *push_state = context;
+    TEST_FAIL(push_state->test_state, "remote disconnect didn't happen");
+}
+
+static void
+test_dns_push_handle_retry_delay(push_test_state_t *push_state, dso_state_t *dso, uint32_t delay)
+{
+    INFO("Got our retry delay, %ums...", delay);
+    push_state->wait_for_remote_disconnect = ioloop_wakeup_create();
+
+    TEST_FAIL_CHECK(push_state->test_state, push_state->wait_for_remote_disconnect != NULL, "can't wait for remote disconnect.");
+
+    // Wait six seconds for remote disconnect, which should happen in five.
+    ioloop_add_wake_event(push_state->wait_for_remote_disconnect, push_state, test_dns_push_remote_disconnect_didnt_happen, NULL, 6 * 1000);
+    push_state->disconnect_expected = dso;
+}
+
+static void
+test_dns_push_address_update(push_test_state_t *push_state, dns_rr_t *rr, const char *name)
+{
+    const char *record_name = "AAAA";
+    char ntop[INET6_ADDRSTRLEN];
+    int num;
+
+    if (rr->type == dns_rrtype_a) {
+        num = push_state->num_a_records;
+        if (rr->ttl != 0xffffffff) {
+            ++push_state->num_a_records;
+        }
+        inet_ntop(AF_INET, &rr->data.a, ntop, sizeof(ntop));
+        record_name = "A";
+    } else {
+        num = push_state->num_aaaa_records;
+        if (rr->ttl != 0xffffffff) {
+            ++push_state->num_aaaa_records;
+        }
+        inet_ntop(AF_INET6, &rr->data.aaaa, ntop, sizeof(ntop));
+    }
+    INFO("%s: %s %s record #%d: %s", name, rr->ttl == 0xffffffff ? "removed" : "added", record_name, num, ntop);
+}
+
+static void
+test_dns_push_update(push_test_state_t *push_state, dns_rr_t *rr)
+{
+    char name[DNS_MAX_NAME_SIZE_ESCAPED + 1];
+    dns_name_print(rr->name, name, sizeof(name));
+
+    if (rr->type == dns_rrtype_soa) {
+        TEST_FAIL_CHECK_STATUS(push_state->test_state, push_state->variant == PUSH_TEST_VARIANT_HARDWIRED,
+                               "SOA received in wrong variant: %s", name);
+        char soaname[DNS_MAX_NAME_SIZE_ESCAPED + 1];
+        TEST_FAIL_CHECK_STATUS(push_state->test_state, !strcmp(name, "default.service.arpa."), "bad name for SOA: %s", name);
+        dns_name_print(rr->data.soa.mname, soaname, sizeof(soaname));
+        INFO("%s in SOA %s ...", name, soaname);
+        // Look up the SRV record for _dns-push-tls._tcp.default.service.arpa, so that we can get the hostname but also
+        // validate that the SRV record is being advertised.
+        push_state->srv_name = strdup("_dns-push-tls._tcp.default.service.arpa.");
+        test_dns_push_send_push_subscribe(push_state, push_state->srv_name, dns_rrtype_srv);
+        test_dns_push_send_push_unsubscribe(push_state, push_state->soa_index);
+    } else if (rr->type == dns_rrtype_a || rr->type == dns_rrtype_aaaa) {
+        test_dns_push_address_update(push_state, rr, name);
+        if (push_state->server_was_crashed) {
+            if (rr->ttl == 0xffffffff) {
+                push_state->num_address_removes++;
+            } else {
+                push_state->num_address_adds_post++;
+            }
+        } else {
+            push_state->num_address_adds_pre++;
+        }
+    } else if (rr->type == dns_rrtype_ptr) {
+        TEST_FAIL_CHECK_STATUS(push_state->test_state, push_state->variant != PUSH_TEST_VARIANT_HARDWIRED,
+                               "PTR received in wrong variant: %s", name);
+        TEST_FAIL_CHECK_STATUS(push_state->test_state, !strcmp(name, "_example._tcp.default.service.arpa."),
+                               "bad name for PTR: %s", name);
+        char ptrname[DNS_MAX_NAME_SIZE_ESCAPED + 1];
+        dns_name_print(rr->data.ptr.name, ptrname, sizeof(ptrname));
+        INFO("%s %s IN PTR %s", rr->ttl == 0xffffffff ? "removed" : "added", name, ptrname);
+        if (push_state->srv_name == NULL) {
+            push_state->srv_name = strdup(ptrname);
+            test_dns_push_send_push_subscribe(push_state, push_state->srv_name, dns_rrtype_srv);
+        }
+        if (push_state->server_was_crashed) {
+            if (rr->ttl == 0xffffffff) {
+                push_state->num_service_removes++;
+            } else {
+                push_state->num_service_adds_post++;
+            }
+        } else {
+            push_state->num_service_adds_pre++;
+        }
+    } else if (rr->type == dns_rrtype_srv) {
+        char hnbuf[DNS_MAX_NAME_SIZE_ESCAPED + 1];
+        dns_name_print(rr->data.ptr.name, hnbuf, sizeof(hnbuf));
+        INFO("%s IN SRV %s ...", name, hnbuf);
+        TEST_FAIL_CHECK_STATUS(push_state->test_state, !strcmp(name, push_state->srv_name), "bad name for SRV: %s", name);
+        // Look up address records for SOA name server name.
+        if (push_state->hostname == NULL) {
+            push_state->hostname = strdup(hnbuf);
+            TEST_FAIL_CHECK_STATUS(push_state->test_state, push_state->hostname != NULL, "no memory for %s", hnbuf);
+            dispatch_async(dispatch_get_main_queue(), ^{
+                test_dns_push_send_push_subscribe(push_state, push_state->hostname, dns_rrtype_a);
+                });
+            dispatch_async(dispatch_get_main_queue(), ^{
+                test_dns_push_send_push_subscribe(push_state, push_state->hostname, dns_rrtype_aaaa);
+            });
+            // At this point the DS queries should have been started, so we can remove them and make sure that works.
+            if (push_state->variant == PUSH_TEST_VARIANT_HARDWIRED) {
+                test_dns_push_send_push_unsubscribe(push_state, push_state->ds_index[0]);
+                test_dns_push_send_push_unsubscribe(push_state, push_state->ds_index[1]);
+            }
+        }
+        push_state->num_srv_records++;
+    } else if (rr->type == dns_rrtype_txt) {
+        char txt_buf[DNS_DATA_SIZE];
+        dns_txt_data_print(txt_buf, DNS_DATA_SIZE, rr->data.txt.len, rr->data.txt.data);
+        INFO("%s IN TXT %s ...", name, txt_buf);
+        push_state->num_txt_records++;
+    } else {
+        INFO("unexpected rrtype for %s in push update: %d", name, rr->type);
+    }
+}
+
+static void
+test_dns_push_send_appropriate_subscribe(push_test_state_t *push_state)
+{
+    if (push_state->variant == PUSH_TEST_VARIANT_HARDWIRED) {
+        push_state->soa_index = push_state->num_subscribe_xids;
+        test_dns_push_send_push_subscribe(push_state, "default.service.arpa", dns_rrtype_soa);
+        push_state->ds_index[0] = push_state->num_subscribe_xids;
+        test_dns_push_send_push_subscribe(push_state, "default.service.arpa", dns_rrtype_ds);
+        push_state->ds_index[1] = push_state->num_subscribe_xids;
+        test_dns_push_send_push_subscribe(push_state, "default.service.arpa", dns_rrtype_ds);
+    } else {
+        test_dns_push_send_push_subscribe(push_state, "_example._tcp.default.service.arpa", dns_rrtype_ptr);
+    }
+    push_state->push_subscribe_sent = true;
+}
+
+static void
+test_dns_push_satisfied_check(push_test_state_t *push_state)
+{
+    // Check to see if we have all the address records we wanted.
+    if (!push_state->have_address_records) {
+        bool satisfied;
+        switch(push_state->variant) {
+        case PUSH_TEST_VARIANT_HARDWIRED:
+            satisfied = push_state->num_a_records != 0 && push_state->num_aaaa_records != 0;
+            break;
+        case PUSH_TEST_VARIANT_DAEMON_CRASH:
+        case PUSH_TEST_VARIANT_MDNS:
+            satisfied = push_state->num_a_records != 0 || push_state->num_aaaa_records != 0;
+            break;
+        case PUSH_TEST_VARIANT_TWO_QUESTIONS:
+            satisfied = push_state->num_srv_records > 0 && push_state->num_txt_records > 0;
+            break;
+        default:
+            satisfied = false;
+        }
+        if (satisfied && push_state->variant == PUSH_TEST_VARIANT_DAEMON_CRASH) {
+            if (!push_state->server_was_crashed) {
+                // If the server hasn't been crashed yet
+                // And we haven't already queued up a crash event
+                if (!push_state->server_is_being_crashed) {
+                    push_state->server_is_being_crashed = true;
+                    // Queue up a crash event
+                    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 2),
+                                   dispatch_get_main_queue(), ^{
+                        push_state->server_was_crashed = true;
+                        push_state->num_a_records = push_state->num_aaaa_records = 0;
+                        dns_service_ref_t *ref = push_state->ptr_sdref;
+                        TEST_FAIL_CHECK(push_state->test_state, ref != NULL, "ptr sdref is gone!");
+                        for (int i = 0; i < push_state->num_txns; i++) {
+                            DNSServiceRefDeallocate(push_state->txns[i]);
+                        }
+                        DNSServiceQueryRecordReply callback = ref->callback.query_record_reply;
+                        callback(ref, 0, 0, kDNSServiceErr_ServiceNotRunning,
+                                 NULL, dns_rrtype_ptr, dns_qclass_in, 0, 0, 0, ref->context);
+                    });
+                }
+                satisfied = false;
+            } else {
+                if (push_state->num_address_removes != push_state->num_address_adds_pre) {
+                    satisfied = false;
+                }
+                if (push_state->num_address_adds_pre != push_state->num_address_adds_post) {
+                    satisfied = false;
+                }
+                INFO("address removes: %d address adds pre: %d address adds post: %d",
+                     push_state->num_address_removes, push_state->num_address_adds_pre,
+                     push_state->num_address_adds_post);
+                if (push_state->num_service_removes != push_state->num_service_adds_pre) {
+                    satisfied = false;
+                }
+                INFO("service removes: %d service adds pre: %d service adds post: %d",
+                     push_state->num_service_removes, push_state->num_service_adds_pre,
+                     push_state->num_service_adds_post);
+            }
+        }
+        if (satisfied) {
+            push_state->have_address_records = true;
+            // If we've been asked to unsubscribe, do that.
+            if (push_state->push_unsubscribe) {
+                test_dns_push_unsubscribe_all(push_state);
+            } else {
+                // Finish any ongoing activities first...
+                dispatch_async(dispatch_get_main_queue(), ^{
+                    TEST_PASSED(push_state->test_state);
+                });
+            }
+        }
+    }
+}
+
+static void
+test_dns_push_dns_response(push_test_state_t *push_state, message_t *message)
+{
+    unsigned offset, max;
+    dns_rr_t rr;
+    uint8_t *message_bytes;
+    bool question = true;
+    int rdata_num = 0;
+    int num_answers = ntohs(message->wire.ancount);
+
+    message_bytes = (uint8_t *)message->wire.data;
+    offset = 0;
+    max = message->length - DNS_HEADER_SIZE;
+    int rr_index = 0;
+    int qdcount = ntohs(message->wire.qdcount);
+    while (offset < max) {
+        INFO("%d %d", offset, max);
+        if (rr_index >= qdcount) {
+            question = false;
+        }
+        if (!dns_rr_parse(&rr, message_bytes, max, &offset, !question, true)) {
+            TEST_FAIL_STATUS(push_state->test_state, "dns RR parse failed on rr %d", rr_index);
+            break;
+        }
+        if (!question) {
+            if (rdata_num < num_answers) {
+                test_dns_push_update(push_state, &rr);
+                rdata_num++;
+            }
+        }
+        dns_name_free(rr.name);
+        rr.name = NULL;
+        dns_rrdata_free(&rr);
+        rr_index++;
+    }
+    test_dns_push_satisfied_check(push_state);
+}
+
+static void
+test_dns_push_dso_message(push_test_state_t *push_state, message_t *message, dso_state_t *dso, bool response)
+{
+    unsigned offset, max;
+    dns_rr_t rr;
+    uint8_t *message_bytes;
+
+    switch(dso->primary.opcode) {
+    case kDSOType_RetryDelay:
+        if (response) {
+            TEST_FAIL(push_state->test_state, "server sent a retry delay TLV as a response.");
+        }
+        dso_retry_delay(dso, &message->wire);
+        break;
+
+    case kDSOType_Keepalive:
+        if (response) {
+            TEST_FAIL_STATUS(push_state->test_state, "keepalive response from server, rcode = %d", dns_rcode_get(&message->wire));
+        } else {
+            INFO("Keepalive from server");
+        }
+
+        // We need to wait for the first keepalive response before sending a DNS push subscribe, since until we get
+        // it we don't have a session. So this actually kicks off the first (possibly only) DNS Push subscribe in the
+        // test.
+        if (!push_state->push_subscribe_sent) {
+            push_state->have_keepalive_response = true;
+            if (!push_state->need_service) {
+                test_dns_push_send_appropriate_subscribe(push_state);
+            }
+        }
+        break;
+
+    case kDSOType_DNSPushSubscribe:
+        if (response) {
+            // This is a protocol error--the response isn't supposed to contain a primary TLV.
+            TEST_FAIL_STATUS(push_state->test_state,
+                             "DNS Push response from server, rcode = %d", dns_rcode_get(&message->wire));
+        } else {
+            INFO("Unexpected DNS Push request from server, rcode = %d", dns_rcode_get(&message->wire));
+        }
+        break;
+
+    case kDSOType_DNSPushUpdate:
+        // DNS Push Updates are never responses.
+        // DNS Push updates are compressed, so we can't just parse data out of the primary--we need to align
+        // our parse with the start of the message data.
+        message_bytes = (uint8_t *)message->wire.data;
+        offset = (unsigned)(dso->primary.payload - message_bytes); // difference can never be greater than sizeof(message->wire).
+        max = offset + dso->primary.length;
+        while (offset < max) {
+            if (!dns_rr_parse(&rr, message_bytes, max, &offset, true, true)) {
+                // Should have emitted an error earlier
+                break;
+            }
+            test_dns_push_update(push_state, &rr);
+            dns_name_free(rr.name);
+            rr.name = NULL;
+            dns_rrdata_free(&rr);
+        }
+
+        test_dns_push_satisfied_check(push_state);
+        break;
+
+    case kDSOType_NoPrimaryTLV: // No Primary TLV
+        if (response) {
+            bool subscribe_acked = false;
+            for (int i = 0; i < push_state->num_subscribe_xids; i++) {
+                if (message->wire.id == htons(push_state->subscribe_xids[i])) {
+                    int rcode = dns_rcode_get(&message->wire);
+                    INFO("DNS Push Subscribe response from server, rcode = %d", rcode);
+                    if (rcode != dns_rcode_noerror) {
+                        TEST_FAIL_STATUS(push_state->test_state, "subscribe for %x failed",
+                                         push_state->subscribe_xids[i]);
+                    }
+                    subscribe_acked = true;
+                }
+            }
+            if (subscribe_acked) {
+            } else if (message->wire.id == push_state->keepalive_xid) {
+                int rcode = dns_rcode_get(&message->wire);
+                INFO("DNS Keepalive response from server, rcode = %d", rcode);
+                exit(0);
+            } else {
+                int rcode = dns_rcode_get(&message->wire);
+                INFO("Unexpected DSO response from server, rcode = %d", rcode);
+            }
+        } else {
+            INFO("DSO request with no primary TLV.");
+            exit(1);
+        }
+        break;
+
+    default:
+        INFO("dso_message: unexpected primary TLV %d", dso->primary.opcode);
+        dso_simple_response(push_state->dso_connection, NULL, &message->wire, dns_rcode_dsotypeni);
+        break;
+    }
+}
+
+static void
+test_dns_push_dso_event_callback(void *context, void *event_context, dso_state_t *dso, dso_event_type_t eventType)
+{
+    push_test_state_t *push_state = context;
+
+    message_t *message;
+    dso_query_receive_context_t *response_context;
+    dso_disconnect_context_t *disconnect_context;
+
+    switch(eventType)
+    {
+    case kDSOEventType_DNSMessage:
+        // We shouldn't get here because we already handled any DNS messages
+        message = event_context;
+        INFO("DNS Message (opcode=%d) received from " PRI_S_SRP, dns_opcode_get(&message->wire),
+             dso->remote_name);
+        break;
+    case kDSOEventType_DNSResponse:
+        // We shouldn't get here because we already handled any DNS messages
+        message = event_context;
+        INFO("DNS Response (opcode=%d) received from " PRI_S_SRP, dns_opcode_get(&message->wire),
+             dso->remote_name);
+        break;
+    case kDSOEventType_DSOMessage:
+        INFO("DSO Message (Primary TLV=%d) received from " PRI_S_SRP,
+               dso->primary.opcode, dso->remote_name);
+        message = event_context;
+        test_dns_push_dso_message(push_state, message, dso, false);
+        break;
+    case kDSOEventType_DSOResponse:
+        INFO("DSO Response (Primary TLV=%d) received from " PRI_S_SRP,
+               dso->primary.opcode, dso->remote_name);
+        response_context = event_context;
+        message = response_context->message_context;
+        test_dns_push_dso_message(push_state, message, dso, true);
+        break;
+
+    case kDSOEventType_Finalize:
+        INFO("Finalize");
+        break;
+
+    case kDSOEventType_Connected:
+        INFO("Connected to " PRI_S_SRP, dso->remote_name);
+        break;
+
+    case kDSOEventType_ConnectFailed:
+        INFO("Connection to " PRI_S_SRP " failed", dso->remote_name);
+        break;
+
+    case kDSOEventType_Disconnected:
+        INFO("Connection to " PRI_S_SRP " disconnected", dso->remote_name);
+        if (dso == push_state->disconnect_expected) {
+            INFO("remote end disconnected as expected.");
+            exit(0);
+        }
+        break;
+    case kDSOEventType_ShouldReconnect:
+        INFO("Connection to " PRI_S_SRP " should reconnect (not for a server)", dso->remote_name);
+        break;
+    case kDSOEventType_Inactive:
+        INFO("Inactivity timer went off, closing connection.");
+        break;
+    case kDSOEventType_Keepalive:
+        INFO("should send a keepalive now.");
+        break;
+    case kDSOEventType_KeepaliveRcvd:
+        if (!push_state->push_subscribe_sent && !push_state->need_service) {
+            test_dns_push_send_appropriate_subscribe(push_state);
+        } else {
+            push_state->have_keepalive_response = true;
+        }
+        INFO("keepalive received.");
+        break;
+    case kDSOEventType_RetryDelay:
+        disconnect_context = event_context;
+        INFO("retry delay received, %d seconds", disconnect_context->reconnect_delay);
+        test_dns_push_handle_retry_delay(push_state, dso, disconnect_context->reconnect_delay);
+        break;
+    }
+}
+
+static void
+test_dns_push_send_push_subscribe(push_test_state_t *push_state, const char *name, int rrtype)
+{
+    struct iovec iov;
+
+    dns_wire_t dns_message;
+    uint8_t *buffer = (uint8_t *)&dns_message;
+    dns_towire_state_t towire;
+    dso_message_t message;
+    int i = push_state->num_subscribe_xids;
+    if (i >= SUBSCRIBE_LIMIT) {
+        TEST_FAIL_STATUS(push_state->test_state, "subscribe xid limit reached: %d", i);
+    }
+    push_state->num_subscribe_xids++;
+
+    if (push_state->test_dns_push) {
+        // DNS Push subscription
+        dso_make_message(&message, buffer, sizeof(dns_message), push_state->dso_connection->dso, false, false, 0, 0, NULL);
+
+        push_state->subscribe_xids[i] = ntohs(dns_message.id);
+        INFO("push subscribe for %s, rrtype %d, xid %x, num %d", name, rrtype, push_state->subscribe_xids[i], i);
+        memset(&towire, 0, sizeof(towire));
+        towire.p = &buffer[DNS_HEADER_SIZE];
+        towire.lim = towire.p + (sizeof(dns_message) - DNS_HEADER_SIZE);
+        towire.message = &dns_message;
+        dns_u16_to_wire(&towire, kDSOType_DNSPushSubscribe);
+        dns_rdlength_begin(&towire);
+        dns_full_name_to_wire(NULL, &towire, name);
+        dns_u16_to_wire(&towire, rrtype);
+        dns_u16_to_wire(&towire, dns_qclass_in);
+        dns_rdlength_end(&towire);
+    } else {
+        // Regular DNS query
+        memset(&dns_message, 0, sizeof(dns_message));
+        dns_message.id = htons((uint16_t)srp_random16());
+        dns_qr_set(&dns_message, 0); // query
+        dns_opcode_set(&dns_message, dns_opcode_query);
+        int num_questions;
+        if (rrtype == dns_rrtype_srv && push_state->variant == PUSH_TEST_VARIANT_TWO_QUESTIONS) {
+            num_questions = 2;
+        } else {
+            num_questions = 1;
+        }
+        dns_message.qdcount = htons(num_questions);
+        memset(&towire, 0, sizeof(towire));
+        towire.p = &buffer[DNS_HEADER_SIZE];
+        towire.lim = towire.p + (sizeof(dns_message) - DNS_HEADER_SIZE);
+        towire.message = &dns_message;
+        dns_name_pointer_t np;
+        dns_full_name_to_wire(&np, &towire, name);
+        dns_u16_to_wire(&towire, rrtype);
+        dns_u16_to_wire(&towire, dns_qclass_in);
+        if (num_questions == 2) {
+            dns_pointer_to_wire(NULL, &towire, &np);
+            dns_u16_to_wire(&towire, dns_rrtype_txt);
+            dns_u16_to_wire(&towire, dns_qclass_in);
+        }
+        push_state->subscribe_xids[i] = ntohs(dns_message.id);
+        INFO("DNS query for %s, rrtype %d, xid %x, num %d", name, rrtype, push_state->subscribe_xids[i], i);
+    }
+
+    memset(&iov, 0, sizeof(iov));
+    iov.iov_len = towire.p - buffer;
+    iov.iov_base = buffer;
+    ioloop_send_message(push_state->dso_connection, NULL, &iov, 1);
+}
+
+static void
+test_dns_push_connected(comm_t *connection, void *context)
+{
+    push_test_state_t *push_state = context;
+    struct iovec iov;
+    INFO("connected");
+    connection->dso = dso_state_create(false, 3, connection->name, test_dns_push_dso_event_callback,
+                                       push_state, NULL, push_state->dso_connection);
+    if (connection->dso == NULL) {
+        ERROR("can't create dso state object.");
+        exit(1);
+    }
+    dns_wire_t dns_message;
+    uint8_t *buffer = (uint8_t *)&dns_message;
+    dns_towire_state_t towire;
+    dso_message_t message;
+    dso_make_message(&message, buffer, sizeof(dns_message), connection->dso, false, false, 0, 0, NULL);
+    memset(&towire, 0, sizeof(towire));
+    towire.p = &buffer[DNS_HEADER_SIZE];
+    towire.lim = towire.p + (sizeof(dns_message) - DNS_HEADER_SIZE);
+    towire.message = &dns_message;
+    dns_u16_to_wire(&towire, kDSOType_Keepalive);
+    dns_rdlength_begin(&towire);
+    dns_u32_to_wire(&towire, 100); // Inactivity timeout
+    dns_u32_to_wire(&towire, 100); // Keepalive interval
+    dns_rdlength_end(&towire);
+
+    memset(&iov, 0, sizeof(iov));
+    iov.iov_len = towire.p - buffer;
+    iov.iov_base = buffer;
+    ioloop_send_message(push_state->dso_connection, NULL, &iov, 1);
+}
+
+static void
+test_dns_push_disconnected(comm_t *UNUSED connection, void *context, int UNUSED error)
+{
+    push_test_state_t *push_state = context;
+    TEST_FAIL(push_state->test_state, "push server disconnect.");
+}
+
+static void
+test_dns_push_datagram_callback(comm_t *connection, message_t *message, void *context)
+{
+    push_test_state_t *push_state = context;
+
+    // If this is a DSO message, see if we have a session yet.
+    switch(dns_opcode_get(&message->wire)) {
+    case dns_opcode_query:
+        test_dns_push_dns_response(push_state, message);
+        return;
+    case dns_opcode_dso:
+        if (connection->dso == NULL) {
+            INFO("dso message received with no DSO object on connection " PRI_S_SRP, connection->name);
+            exit(1);
+        }
+        dso_message_received(connection->dso, (uint8_t *)&message->wire, message->length, message);
+        return;
+    }
+    INFO("datagram on connection " PRI_S_SRP " not handled, type = %d.",
+         connection->name, dns_opcode_get(&message->wire));
+}
+
+static void
+test_dns_push_ready(void *context, uint16_t UNUSED port)
+{
+    push_test_state_t *push_state = context;
+    test_state_t *state = push_state->test_state;
+
+    addr_t address;
+    memset(&address, 0, sizeof(address));
+    address.sa.sa_family = AF_INET;
+    address.sin.sin_port = htons(8530);
+    address.sin.sin_addr.s_addr = htonl(0x7f000001);  // localhost.
+                                                      // tls, stream, stable, opportunistic
+    push_state->dso_connection = ioloop_connection_create(&address, true,   true,   true, true,
+                                                          test_dns_push_datagram_callback, test_dns_push_connected,
+                                                          test_dns_push_disconnected, NULL, push_state);
+    TEST_FAIL_CHECK(state, push_state->dso_connection != NULL, "Unable to create dso connection.");
+}
+
+static bool
+test_listen_longevity_dnssd_proxy_configure(void)
+{
+    dnssd_proxy_udp_port= 53000;
+    dnssd_proxy_tcp_port = 53000;
+    dnssd_proxy_tls_port = 8530;
+    return true;
+}
+
+static bool
+test_dns_push_query_callback_intercept(DNSServiceRef sdRef, DNSServiceFlags flags, uint32_t interfaceIndex,
+                                       DNSServiceErrorType errorCode, const char *fullname, uint16_t rrtype,
+                                       uint16_t rrclass, uint16_t rdlen, const void *vrdata, uint32_t ttl, void *context)
+{
+    dns_service_ref_t *ref = sdRef;
+    const uint8_t *rdata = vrdata;
+    const uint8_t *new_rdata = rdata;
+    uint8_t rdbuf[16];
+
+    if (rrtype == dns_rrtype_a) {
+        if (rdata[0] == 169 && rdata[1] == 254) {
+            new_rdata = rdbuf;
+            rdbuf[0] = 10;
+            rdbuf[1] = 255;
+            rdbuf[2] = rdata[2];
+            rdbuf[3] = rdata[3];
+        }
+    } else if (rrtype == dns_rrtype_aaaa) {
+        if (rdata[0] == 0xfe && rdata[1] == 0x80) {
+            new_rdata = rdbuf;
+            rdbuf[0] = 0xfc; // Change to unused 0xFC address space
+            memcpy(&rdbuf[1], rdata + 1, 15); // Keep the rest.
+        }
+    }
+    DNSServiceQueryRecordReply callback = ref->callback.query_record_reply;
+    callback(ref, flags, interfaceIndex, errorCode, fullname, rrtype, rrclass, rdlen, new_rdata, ttl, context);
+    return false;
+}
+
+static void test_dns_push_register_example_service(push_test_state_t *push_state);
+
+static void
+test_dns_push_register_callback(const DNSServiceRef UNUSED sd_ref, const DNSServiceFlags UNUSED flags,
+                           const DNSServiceErrorType error, const char *const name, const char *const reg_type,
+                           const char *const domain, void *const context)
+{
+    push_test_state_t *push_state = context;
+    test_state_t *state = push_state->test_state;
+
+    if (error == kDNSServiceErr_ServiceNotRunning) {
+        INFO("example service deregistered due to server exit");
+        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC / 10), // 100ms
+                       dispatch_get_main_queue(), ^{
+            test_dns_push_register_example_service(push_state);
+                       });
+    } else {
+        TEST_FAIL_CHECK_STATUS(state, error == kDNSServiceErr_NoError, "example service registration failed: %d", error);
+        INFO("example service registered successfully -- %s.%s%s", name, reg_type, domain);
+        if (push_state->need_service) {
+            push_state->need_service = false;
+            if (push_state->have_keepalive_response) {
+                test_dns_push_send_appropriate_subscribe(push_state);
+            }
+        }
+    }
+}
+
+static void
+test_dns_push_register_example_service(push_test_state_t *push_state)
+{
+    uint8_t txt_data[] = {
+        0x08, 0x53, 0x49, 0x49, 0x3D, 0x35, 0x30, 0x30, 0x30, 0x07,
+        0x53, 0x41, 0x49, 0x3D, 0x33, 0x30, 0x30, 0x03, 0x54, 0x3D, 0x30 };
+
+    int ret = DNSServiceRegister(&push_state->register_ref, 0, kDNSServiceInterfaceIndexAny, NULL,
+                                 "_example._tcp", NULL, NULL, 12345, sizeof(txt_data), txt_data,
+                                 test_dns_push_register_callback, push_state);
+    TEST_FAIL_CHECK(push_state->test_state, ret == kDNSServiceErr_NoError, "failed to register example service.");
+    DNSServiceSetDispatchQueue(push_state->register_ref, dispatch_get_main_queue());
+}
+
+static DNSServiceErrorType
+test_dns_push_crash_intercept(test_state_t *state, DNSServiceRef *sdRef, DNSServiceFlags flags, uint32_t interfaceIndex,
+                         const char *fullname, uint16_t rrtype, uint16_t rrclass, DNSServiceAttribute const *attr,
+                         DNSServiceQueryRecordReply callBack, void UNUSED *context)
+{
+    dns_service_ref_t *ref = context;
+    DNSServiceErrorType status = DNSServiceQueryRecordWithAttribute(sdRef, flags, interfaceIndex, fullname,
+                                                                    rrtype, rrclass, attr, callBack, ref);
+    // We're going to signal the daemon crash on the PTR query.
+    push_test_state_t *push_state = state->context;
+    if (status == kDNSServiceErr_NoError && rrtype == dns_rrtype_ptr) {
+        push_state->ptr_sdref = ref;
+    } else {
+        if (push_state->num_txns < TXN_LIMIT) {
+            push_state->txns[push_state->num_txns++] = ref;
+        }
+    }
+    return status;
+}
+
+void
+test_dns_push(test_state_t *next_test, int variant)
+{
+    extern srp_server_t *srp_servers;
+    test_state_t *state = NULL;
+    bool register_example_service = false;
+    bool push = true;
+    if (variant == PUSH_TEST_VARIANT_HARDWIRED) {
+        const char *hardwired =
+            "  The goal of this test is to create DNS Push connection to the test server and attempt to\n"
+            "  look up a name that goes through the hardwired query path. If we get a response, the test\n"
+            "  succeeded.";
+        state = test_state_create(srp_servers, "DNS Push Hardwired test", NULL, hardwired, NULL);
+    } else if (variant == PUSH_TEST_VARIANT_MDNS) {
+        const char *mdns =
+            "  The goal of this test is to create DNS Push connection to the test server and attempt to\n"
+            "  look up a name that goes through the local (mDNS) query path. If we get a response, the test\n"
+            "  succeeded.";
+        state = test_state_create(srp_servers, "DNS Push Local test", NULL, mdns, NULL);
+        register_example_service = true;
+    } else if (variant == PUSH_TEST_VARIANT_DNS_MDNS) {
+        const char *mdns =
+            "  The goal of this test is to create DSO connection to the test server and send a DNS query to\n"
+            "  look up a name that goes through the local (mDNS) query path. If we get a response, the test\n"
+            "  succeeded.";
+        state = test_state_create(srp_servers, "DNS Local test", NULL, mdns, NULL);
+        register_example_service = true;
+        push = false;
+        variant = PUSH_TEST_VARIANT_MDNS;
+    } else if (variant == PUSH_TEST_VARIANT_TWO_QUESTIONS) {
+        const char *two =
+            "  The goal of this test is to create DSO connection to the test server and send a DNS query with\n"
+            "  two questions on the same name, for a TXT and an SRV record. We will register a matter service to\n"
+            "  discover.  If we get a well-formed response with answers for both service types, the test\n"
+            "  succeeded.";
+        state = test_state_create(srp_servers, "DNS Local two-question test", NULL, two, NULL);
+        register_example_service = true;
+        push = false;
+        variant = PUSH_TEST_VARIANT_TWO_QUESTIONS;
+    } else if (variant == PUSH_TEST_VARIANT_DNS_HARDWIRED) {
+        const char *hardwired =
+            "  The goal of this test is to create DSO connection to the test server and send a DNS query that\n"
+            "  looks up a name that goes through the hardwired query path. If we get a response, the test\n"
+            "  succeeded.";
+        state = test_state_create(srp_servers, "DNS query Hardwired test", NULL, hardwired, NULL);
+        push = false;
+        variant = PUSH_TEST_VARIANT_HARDWIRED;
+    } else if (variant == PUSH_TEST_VARIANT_DNS_CRASH) {
+        const char *crash =
+        "  The goal of this test is to create DSO connection to the test server and attempt to\n"
+        "  look up a name that goes through the local (mDNS) query path. Once we have a result, we fake\n"
+        "  an mDNSResponder crash and make sure the query is successfully restarted.";
+        state = test_state_create(srp_servers, "DNS query daemon crash test", NULL, crash, NULL);
+        state->query_record_intercept = test_dns_push_crash_intercept;
+        register_example_service = true;
+        variant = PUSH_TEST_VARIANT_DAEMON_CRASH;
+        push = false;
+    } else if (variant == PUSH_TEST_VARIANT_DAEMON_CRASH) {
+        const char *crash =
+            "  The goal of this test is to create DNS Push connection to the test server and attempt to\n"
+            "  look up a name that goes through the local (mDNS) query path. Once we have a result, we fake\n"
+            "  an mDNSResponder crash and make sure the query is successfully restarted.";
+        state = test_state_create(srp_servers, "DNS Push daemon crash test", NULL, crash, NULL);
+        state->query_record_intercept = test_dns_push_crash_intercept;
+        register_example_service = true;
+    }
+    state->next = next_test;
+    push_test_state_t *push_state = calloc(1, sizeof (*push_state));
+    TEST_FAIL_CHECK(state, push_state != NULL, "no memory for test-specific state.");
+    state->context = push_state;
+    push_state->test_state = state;
+    push_state->variant = variant;
+    push_state->test_dns_push = push;
+
+    // Might as well always test
+    push_state->push_unsubscribe = true;
+
+    srp_test_dnssd_tls_listener_ready = test_dns_push_ready;
+    srp_test_tls_listener_context = push_state;
+    srp_test_dso_message_finished = test_dns_push_dso_message_finished;
+
+    srp_proxy_init("local");
+    srp_test_enable_stub_router(state, srp_servers);
+    state->dns_service_query_callback_intercept = test_dns_push_query_callback_intercept;
+    state->dnssd_proxy_configurer = test_listen_longevity_dnssd_proxy_configure;
+    TEST_FAIL_CHECK(state, init_dnssd_proxy(srp_servers), "failed to setup dnssd-proxy");
+
+    if (register_example_service) {
+        push_state->need_service = true;
+        test_dns_push_register_example_service(push_state);
+    }
+
+    // Test should not take longer than ten seconds.
+    srp_test_state_add_timeout(state, 20);
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/test/tests/getifaddrs.c b/ServiceRegistration/test/tests/getifaddrs.c
new file mode 100644
index 0000000..c3198be
--- /dev/null
+++ b/ServiceRegistration/test/tests/getifaddrs.c
@@ -0,0 +1,122 @@
+/* getifaddrs.c
+ *
+ * Copyright (c) 2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains unit tests for ioloop_map_interface_addresses().
+ */
+
+#include "srp.h"
+#include <dns_sd.h>
+#include <arpa/inet.h>
+#include "srp-test-runner.h"
+#include "srp-api.h"
+#include "dns-msg.h"
+#include "ioloop.h"
+#include "srp-mdns-proxy.h"
+#include "test-api.h"
+#include "srp-proxy.h"
+#include "srp-mdns-proxy.h"
+#include "test-dnssd.h"
+#include "test.h"
+#include "dnssd-proxy.h"
+#define DNSMessageHeader dns_wire_t
+#include "dso.h"
+#include "dso-utils.h"
+
+#define SUBSCRIBE_LIMIT 4 // SOA + SRV + A + AAAA || PTR + SRV + A + AAAA
+typedef struct ifaddr_test_state ifaddr_test_state_t;
+struct ifaddr_test_state {
+    test_state_t *test_state;
+    struct ifaddrs *real_ifaddrs;
+    bool lladdr_removed, lladdr_added;
+    interface_address_state_t *ifaddr_state;
+};
+
+static void
+test_ifaddrs_changed_l2addr(srp_server_t UNUSED *server_state, void *context, const char UNUSED *name,
+                            const addr_t *address, const addr_t UNUSED *netmask, uint32_t UNUSED flags,
+                            enum interface_address_change event_type)
+{
+    ifaddr_test_state_t *its = context;
+    if (address->sa.sa_family == AF_LINK) {
+        if (event_type == interface_address_deleted) {
+            its->lladdr_removed = true;
+        } else if (event_type == interface_address_added) {
+            its->lladdr_added = true;
+
+        }
+    }
+}
+
+static void
+test_ifaddrs_ignore(srp_server_t UNUSED *server_state, void UNUSED *context, const char UNUSED *name,
+                    const addr_t UNUSED *address, const addr_t UNUSED *netmask, uint32_t UNUSED flags,
+                    enum interface_address_change UNUSED event_type)
+{
+}
+
+static int
+test_ifaddrs_get(srp_server_t UNUSED *server_state, struct ifaddrs **ifaddrs, void *context)
+{
+    ifaddr_test_state_t *its = context;
+    *ifaddrs = its->real_ifaddrs;
+    return 0;
+}
+
+static void
+test_ifaddrs_free(srp_server_t UNUSED *server_state, struct ifaddrs UNUSED *ifaddrs, void UNUSED *context)
+{
+}
+
+void
+test_ifaddrs_start(test_state_t *next_test)
+{
+    extern srp_server_t *srp_servers;
+    test_state_t *state = NULL;
+    const char *summary =
+        "  The goal of this test is to exercise the various behaviors of ioloop_map_interface_addresses when\n"
+        "  used to track the coming and going of interfaces and addresses on interfaces.\n";
+    state = test_state_create(srp_servers, "ioloop_map_interface_addresses test", NULL, summary, NULL);
+    state->next = next_test;
+    state->getifaddrs = test_ifaddrs_get;
+    state->freeifaddrs = test_ifaddrs_free;
+
+    ifaddr_test_state_t *its = calloc(1, sizeof (*its));
+    TEST_FAIL_CHECK(state, its != NULL, "no memory for test-specific state.");
+    its->test_state = state;
+
+    srp_proxy_init("local");
+
+    TEST_FAIL_CHECK_STATUS(state, getifaddrs(&its->real_ifaddrs) == 0, "getifaddrs failed: %s", strerror(errno));
+    TEST_FAIL_CHECK(state, ioloop_map_interface_addresses_here(srp_servers, &its->ifaddr_state, NULL, its, test_ifaddrs_ignore), "initial map call failed");
+    for (struct ifaddrs *ifp = its->real_ifaddrs; ifp != NULL; ifp = ifp->ifa_next) {
+        if (ifp->ifa_addr != NULL && ifp->ifa_addr->sa_family == AF_LINK) {
+            struct sockaddr_dl *sdl = (struct sockaddr_dl *)ifp->ifa_addr;
+            LLADDR(sdl)[0] = ~LLADDR(sdl)[0];
+        }
+    }
+    TEST_FAIL_CHECK(state, ioloop_map_interface_addresses_here(srp_servers, &its->ifaddr_state, NULL, its, test_ifaddrs_changed_l2addr), "changed_l2addr map call failed");
+    TEST_FAIL_CHECK(state, its->lladdr_removed && its->lladdr_added, "link local address wasn't updated");
+    TEST_PASSED(state);
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/test/tests/lease-expiry.c b/ServiceRegistration/test/tests/lease-expiry.c
new file mode 100644
index 0000000..83c6e8e
--- /dev/null
+++ b/ServiceRegistration/test/tests/lease-expiry.c
@@ -0,0 +1,145 @@
+/* registration-lease-expiry.c
+ *
+ * Copyright (c) 2023-2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains the SRP server test runner.
+ */
+
+#include "srp.h"
+#include <dns_sd.h>
+#include "srp-test-runner.h"
+#include "srp-api.h"
+#include "dns-msg.h"
+#include "ioloop.h"
+#include "srp-mdns-proxy.h"
+#include "test-api.h"
+#include "srp-proxy.h"
+#include "srp-mdns-proxy.h"
+#include "test-dnssd.h"
+#include "test.h"
+
+#define LEASE_TIME 10
+
+static void
+test_lease_expiry_register_evaluate(test_state_t *state)
+{
+    adv_instance_t *instance = srp_test_server_find_instance(state, TEST_INSTANCE_NAME, TEST_SERVICE_TYPE);
+    TEST_FAIL_CHECK(state, instance != NULL, "instance is not found in cache");
+    dns_service_event_t *register_event = dns_service_find_first_register_event_by_name_and_type(state->primary,
+                                                                                                 TEST_INSTANCE_NAME,
+                                                                                                 TEST_SERVICE_TYPE);
+    TEST_FAIL_CHECK(state, register_event != NULL, "failed to register service");
+}
+
+static void
+test_lease_expiry_expire_evaluate(test_state_t *state)
+{
+    adv_instance_t *instance = srp_test_server_find_instance(state, TEST_INSTANCE_NAME, TEST_SERVICE_TYPE);
+    TEST_FAIL_CHECK(state, instance == NULL, "instance is still in cache");
+    dns_service_event_t *deallocate_event = dns_service_find_ref_deallocate_event(state->primary);
+    TEST_FAIL_CHECK(state, deallocate_event != NULL, "failed to remove service");
+    TEST_PASSED(state);
+}
+
+static void
+test_lease_expiry_callback(DNSServiceRef sdref, DNSServiceFlags UNUSED flags, DNSServiceErrorType errorCode,
+                                 const char *name, const char *regtype, const char *UNUSED domain, void *context)
+{
+    srp_server_t *server_state = context;
+    test_state_t *state = server_state->test_state;
+
+    INFO("Register Reply for %s . %s: %d", name, regtype, errorCode);
+    INFO("state = %p", state);
+
+    if (errorCode != kDNSServiceErr_NoError) {
+        TEST_FAIL_STATUS(state, "registration failed: srp_client_register callback returned %d", errorCode);
+    }
+
+    // Allow time for the mDNS registration to finish
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 2), dispatch_get_main_queue(), ^{
+        void *io_context = state->current_io_context;
+        TEST_FAIL_CHECK(state, io_context != NULL, "NULL io_context");
+        srp_cancel_wakeup(server_state, io_context);// cancel the lease renewal
+        test_lease_expiry_register_evaluate(state);
+
+        // Allow time for the lease to expire and check the registration again
+        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * LEASE_TIME), dispatch_get_main_queue(), ^{
+            srp_client_ref_deallocate(sdref);
+            test_lease_expiry_expire_evaluate(state);
+        });
+    });
+}
+
+static void
+test_lease_expiry_ready(void *context, uint16_t UNUSED port)
+{
+    srp_server_t *state = context;
+
+    int ret = srp_host_init(state);
+    srp_set_lease_times(LEASE_TIME, LEASE_TIME);
+    TEST_FAIL_CHECK(state->test_state, ret == kDNSServiceErr_NoError, "srp_host_init failed");
+
+    DNSServiceRef ref;
+    INFO("Lease expiry test");
+    char txt_buf[128];
+    TXTRecordRef txt;
+
+    TXTRecordCreate(&txt, sizeof(txt_buf), txt_buf);
+    TXTRecordSetValue(&txt, "foo", 1, "1");
+    TXTRecordSetValue(&txt, "bar", 3, "1.1");
+    const char *txt_data = TXTRecordGetBytesPtr(&txt);
+    int txt_len = TXTRecordGetLength(&txt);
+
+    // Create a DNSSD client
+    ret = srp_client_register(&ref, 0 /* flags */, 0 /* interfaceIndex */,
+                              TEST_INSTANCE_NAME /* name */, TEST_SERVICE_TYPE /* regType */,
+                              NULL /* domain */, TEST_HOST_NAME /* host */, 1234, txt_len, txt_data,
+                              test_lease_expiry_callback, state);
+    TEST_FAIL_CHECK_STATUS(state->test_state, ret == kDNSServiceErr_NoError, "srp_client_register returned %d", ret);
+    srp_test_network_localhost_start(state->test_state);
+}
+
+void
+test_lease_expiry_start(test_state_t *next_state)
+{
+    extern srp_server_t *srp_servers;
+    const char *description =
+        "    The goal of this test is to validate that when the lease provided by the client expires,\n"
+        "    the service and host entries are removed. The test first sets the lease time that the client\n"
+        "    requests to 10 seconds. It then registers a host and service. Assuming that the registration\n"
+        "    is successful, it stops further updates and then waits for the lease to expire. When the lease\n"
+        "    expires, it checks to see that the records and service that were registered have all been\n"
+        "    deallocated.";
+    test_state_t *state = test_state_create(srp_servers, "Lease Expiry test", NULL, description, NULL);
+
+    srp_proxy_init("local");
+    srp_test_enable_stub_router(state, srp_servers);
+    state->primary->min_lease_time = LEASE_TIME;
+    state->srp_listener = srp_proxy_listen(NULL, 0, NULL, test_lease_expiry_ready, NULL, NULL, NULL, state->primary);
+    TEST_FAIL_CHECK(state, state->srp_listener != NULL, "listener create failed");
+    state->next = next_state;
+
+    // Test should not take longer than 2*LEASE_TIME.
+    srp_test_state_add_timeout(state, 2 * LEASE_TIME);
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/test/tests/lease-renewal.c b/ServiceRegistration/test/tests/lease-renewal.c
new file mode 100644
index 0000000..e63cd56
--- /dev/null
+++ b/ServiceRegistration/test/tests/lease-renewal.c
@@ -0,0 +1,137 @@
+/* registration-lease-expiry.c
+ *
+ * Copyright (c) 2023-2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains the SRP server test runner.
+ */
+
+#include "srp.h"
+#include <dns_sd.h>
+#include "srp-test-runner.h"
+#include "srp-api.h"
+#include "dns-msg.h"
+#include "ioloop.h"
+#include "srp-mdns-proxy.h"
+#include "test-api.h"
+#include "srp-proxy.h"
+#include "srp-mdns-proxy.h"
+#include "test-dnssd.h"
+#include "test.h"
+
+#define LEASE_TIME 15
+
+static void
+test_lease_renew_evaluate(test_state_t *state)
+{
+    adv_instance_t *instance = srp_test_server_find_instance(state, TEST_INSTANCE_NAME, TEST_SERVICE_TYPE);
+    TEST_FAIL_CHECK(state, instance != NULL, "instance is not found in cache");
+
+    dns_service_event_t *register_event = dns_service_find_first_register_event_by_name_and_type(state->primary,
+                                                                                                 TEST_INSTANCE_NAME,
+                                                                                                 TEST_SERVICE_TYPE);
+    TEST_FAIL_CHECK(state, register_event != NULL, "failed to initially register service");
+    dns_service_event_t *update_event = dns_service_find_update_for_register_event(state->primary,
+                                                                                   register_event, NULL);
+    // TSR update due to lease renewal is all that we are looking for.
+    while (update_event != NULL && update_event->rdata != NULL) {
+        update_event = dns_service_find_update_for_register_event(state->primary, register_event, update_event);
+    }
+    TEST_FAIL_CHECK(state, update_event != NULL, "initial registration is successful but lease renewal is not received.");
+    TEST_PASSED(state);
+}
+
+static void
+test_lease_renewal_callback(DNSServiceRef sdref, DNSServiceFlags UNUSED flags, DNSServiceErrorType errorCode,
+                                 const char *name, const char *regtype, const char *UNUSED domain, void *context)
+{
+    srp_server_t *server_state = context;
+    test_state_t *state = server_state->test_state;
+
+    INFO("Register Reply for %s . %s: %d", name, regtype, errorCode);
+    INFO("state = %p", state);
+
+    if (errorCode != kDNSServiceErr_NoError) {
+        TEST_FAIL_STATUS(state, "registration failed: srp_client_register callback returned %d", errorCode);
+    }
+
+    // Allow time for the mDNS registration to finish and for the original lease to end
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * (2+LEASE_TIME)), dispatch_get_main_queue(), ^{
+        srp_client_ref_deallocate(sdref);
+        srp_network_state_stable(NULL); // Discontinue SRP client
+        test_lease_renew_evaluate(state);
+    });
+}
+
+
+static void
+test_lease_renewal_ready(void *context, uint16_t UNUSED port)
+{
+    srp_server_t *state = context;
+
+    int ret = srp_host_init(state);
+    srp_set_lease_times(LEASE_TIME, LEASE_TIME);
+    TEST_FAIL_CHECK(state->test_state, ret == kDNSServiceErr_NoError, "srp_host_init failed");
+
+    DNSServiceRef ref;
+    INFO("Lease renewal test");
+    char txt_buf[128];
+    TXTRecordRef txt;
+
+    TXTRecordCreate(&txt, sizeof(txt_buf), txt_buf);
+    TXTRecordSetValue(&txt, "foo", 1, "1");
+    TXTRecordSetValue(&txt, "bar", 3, "1.1");
+    const char *txt_data = TXTRecordGetBytesPtr(&txt);
+    int txt_len = TXTRecordGetLength(&txt);
+
+    // Create a DNSSD client
+    ret = srp_client_register(&ref, 0 /* flags */, 0 /* interfaceIndex */,
+                              TEST_INSTANCE_NAME /* name */, TEST_SERVICE_TYPE /* regType */,
+                              NULL /* domain */, TEST_HOST_NAME /* host */, 1234, txt_len, txt_data,
+                              test_lease_renewal_callback, state);
+    TEST_FAIL_CHECK_STATUS(state->test_state, ret == kDNSServiceErr_NoError, "srp_client_register returned %d", ret);
+    srp_test_network_localhost_start(state->test_state);
+}
+
+void
+test_lease_renewal_start(test_state_t *next_state)
+{
+    extern srp_server_t *srp_servers;
+    const char *description =
+        "    The goal of this test is to validate that the lease is renewed and TSR record is updated.\n"
+        "    The test first sets the lease time that the client requests to 10 seconds. It then registers\n"
+        "    a host and service. Assuming that the registration is successful, it then waits until the\n"
+        "    first lease ends and check if lease is renewed and TSR record is updated upon the request\n"
+        "    of the client.";
+    test_state_t *state = test_state_create(srp_servers, "Lease Renewal test", NULL, description, NULL);
+
+    srp_proxy_init("local");
+    srp_test_enable_stub_router(state, srp_servers);
+    state->primary->min_lease_time = LEASE_TIME;
+    state->srp_listener = srp_proxy_listen(NULL, 0, NULL, test_lease_renewal_ready, NULL, NULL, NULL, state->primary);
+    TEST_FAIL_CHECK(state, state->srp_listener != NULL, "listener create failed");
+
+    state->next = next_state;
+
+    // Test should not take longer than 2*LEASE_TIME.
+    srp_test_state_add_timeout(state, 2 * LEASE_TIME);
+}
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/test/tests/listen-longevity.c b/ServiceRegistration/test/tests/listen-longevity.c
new file mode 100644
index 0000000..9192be9
--- /dev/null
+++ b/ServiceRegistration/test/tests/listen-longevity.c
@@ -0,0 +1,198 @@
+/* listen-longevity.c
+ *
+ * Copyright (c) 2023-2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains the SRP server test runner.
+ */
+
+#include "srp.h"
+#include <dns_sd.h>
+#include "srp-test-runner.h"
+#include "srp-api.h"
+#include "dns-msg.h"
+#include "ioloop.h"
+#include "srp-mdns-proxy.h"
+#include "test-api.h"
+#include "srp-proxy.h"
+#include "srp-mdns-proxy.h"
+#include "test-dnssd.h"
+#include "test.h"
+#include "dnssd-proxy.h"
+
+static comm_t *
+test_listen_longevity_dnssd_udp_listener_find(void)
+{
+// Find the UDP listener if there is one.
+    for (int i = 0; i < dnssd_proxy_num_listeners; i++) {
+        comm_t *l = dnssd_proxy_listeners[i];
+        if (l != NULL && !strcmp(l->name, "DNS over UDP")) {
+            return l;
+        }
+    }
+    return NULL;
+}
+
+
+static void
+test_listen_longevity_test_evaluate(test_state_t *state)
+{
+    dns_service_event_t *register_event = dns_service_find_first_register_event_by_name_and_type(state->primary,
+                                                                                                 TEST_INSTANCE_NAME,
+                                                                                                 TEST_SERVICE_TYPE);
+    TEST_FAIL_CHECK(state, register_event != NULL, "failed to initially register service");
+    dns_service_event_t *update_event = dns_service_find_update_for_register_event(state->primary,
+                                                                                   register_event, NULL);
+    TEST_FAIL_CHECK(state, update_event != NULL, "failed to correctly update service");
+    // An update event with a NULL rdata is a TSR update, which isn't what we're looking for.
+    while (update_event != NULL && update_event->rdata == NULL) {
+        INFO("skipping TSR update");
+        update_event = dns_service_find_update_for_register_event(state->primary, register_event, update_event);
+    }
+    TEST_FAIL_CHECK(state, update_event != NULL, "failed to correctly update service");
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 30), dispatch_get_main_queue(), ^{
+            comm_t *listener = test_listen_longevity_dnssd_udp_listener_find();
+            TEST_FAIL_CHECK(state, listener != NULL && listener->io.fd != -1, "listener was canceled.");
+            TEST_PASSED(state);
+        });
+}
+
+static void
+test_listen_longevity_callback(DNSServiceRef sdref, DNSServiceFlags UNUSED flags, DNSServiceErrorType errorCode,
+                                 const char *name, const char *regtype, const char *UNUSED domain, void *context)
+{
+    srp_server_t *server_state = context;
+    test_state_t *state = server_state->test_state;
+    static bool updated_text_record = false;
+
+    INFO("Register Reply for %s . %s: %d", name, regtype, errorCode);
+    INFO("state = %p", state);
+
+    if (errorCode != kDNSServiceErr_NoError) {
+        if (updated_text_record) {
+            TEST_FAIL_STATUS(state, "text record update failed: srp_client_register callback returned %d", errorCode);
+        } else {
+            TEST_FAIL_STATUS(state, "initial registration failed: srp_client_register callback returned %d", errorCode);
+        }
+    }
+
+    // if we have already updated text record, there's nothing to do;
+    // otherwise, schedule a txt update.
+    if (updated_text_record) {
+        return;
+    }
+
+    // Allow time for the mDNS registration to finish
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 2), dispatch_get_main_queue(), ^{
+            char txt_buf[128];
+            TXTRecordRef txt;
+
+            TXTRecordCreate(&txt, sizeof(txt_buf), txt_buf);
+            TXTRecordSetValue(&txt, "foo", 1, "1");
+            TXTRecordSetValue(&txt, "bar", 3, "2.1");
+            const char *txt_data = TXTRecordGetBytesPtr(&txt);
+            int txt_len = TXTRecordGetLength(&txt);
+
+            int ret = srp_client_update_record(sdref, NULL, 0, txt_len, txt_data, 0);
+            TEST_FAIL_CHECK_STATUS(state, ret == kDNSServiceErr_NoError,
+                                   "text record update failed: srp_client_update_record returned %d", ret);
+            srp_network_state_stable(NULL);
+            updated_text_record = true;
+
+            // We will not get another callback because of a TXT record update unless it produces a conflict.
+            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 2), dispatch_get_main_queue(), ^{
+                    srp_client_ref_deallocate(sdref);
+                    srp_network_state_stable(NULL); // Discontinue SRP client
+                    // Allow any cleanup to happen and then evaluate results.
+                    dispatch_async(dispatch_get_main_queue(), ^{
+                            test_listen_longevity_test_evaluate(state);
+                        });
+                });
+        });
+}
+
+static void
+test_listen_longevity_ready(srp_server_t *state)
+{
+    int ret = srp_host_init(state);
+    TEST_FAIL_CHECK(state->test_state, ret == kDNSServiceErr_NoError, "srp_host_init failed");
+
+    DNSServiceRef ref;
+    INFO("Change text record test");
+    char txt_buf[128];
+    TXTRecordRef txt;
+
+    TXTRecordCreate(&txt, sizeof(txt_buf), txt_buf);
+    TXTRecordSetValue(&txt, "foo", 1, "1");
+    TXTRecordSetValue(&txt, "bar", 3, "1.1");
+    const char *txt_data = TXTRecordGetBytesPtr(&txt);
+    int txt_len = TXTRecordGetLength(&txt);
+
+    // Create a DNSSD client
+    ret = srp_client_register(&ref, 0 /* flags */, 0 /* interfaceIndex */,
+                              TEST_INSTANCE_NAME /* name */, TEST_SERVICE_TYPE /* regType */,
+                              NULL /* domain */, TEST_HOST_NAME /* host */, 1234, txt_len, txt_data,
+                              test_listen_longevity_callback, state);
+    TEST_FAIL_CHECK_STATUS(state->test_state, ret == kDNSServiceErr_NoError, "srp_client_register returned %d", ret);
+    srp_test_network_localhost_start(state->test_state);
+}
+
+static bool
+test_listen_longevity_dnssd_proxy_configure(void)
+{
+    dnssd_proxy_udp_port= 53000;
+    dnssd_proxy_tcp_port = 53000;
+    dnssd_proxy_tls_port = 8530;
+    return true;
+}
+
+void
+test_listen_longevity_start(test_state_t *next_test)
+{
+    extern srp_server_t *srp_servers;
+    const char *description =
+        "  The goal of this test is to see that when we start the dnssd proxy UDP listener and deliver\n"
+        "  an SRP registration to it, that the listener is not canceled after 30 seconds. This test is\n"
+        "  in place because of rdar://124631151 (Unable to pair matter thread accessory ...) which was\n"
+        "  cause by erroneously starting a tracker idle timeout when processing an incoming SRP\n"
+        "  registration on port 53, which is where we receive anycast updates.  The test fails if\n"
+        "  after 30 seconds the listener has been canceled.\n";
+    test_state_t *state = test_state_create(srp_servers, "Listener Longevity test", NULL, description, NULL);
+
+    srp_proxy_init("local");
+    srp_test_enable_stub_router(state, srp_servers);
+    state->dnssd_proxy_configurer = test_listen_longevity_dnssd_proxy_configure;
+	TEST_FAIL_CHECK(state, init_dnssd_proxy(srp_servers), "failed to setup dnssd-proxy");
+    state->next = next_test;
+
+    // Use the UDP listener as the SRP listener.
+    state->srp_listener = test_listen_longevity_dnssd_udp_listener_find();
+    TEST_FAIL_CHECK(state, state->srp_listener != NULL, "no dnssd UDP listener.");
+
+    // Test should not take longer than ten seconds.
+    srp_test_state_add_timeout(state, 45);
+
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC / 5), dispatch_get_main_queue(), ^{
+        test_listen_longevity_ready(srp_servers);
+    });
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/test/tests/multi-host-record.c b/ServiceRegistration/test/tests/multi-host-record.c
new file mode 100644
index 0000000..68f0ad2
--- /dev/null
+++ b/ServiceRegistration/test/tests/multi-host-record.c
@@ -0,0 +1,66 @@
+/* multi-host-record.c
+ *
+ * Copyright (c) 2023 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains a test for validating the correct propagation of multiple host records in srp replication.
+ */
+
+#include "srp.h"
+#include <dns_sd.h>
+#include "srp-test-runner.h"
+#include "srp-api.h"
+#include "dns-msg.h"
+#include "ioloop.h"
+#include "srp-mdns-proxy.h"
+#include "test-api.h"
+#include "srp-proxy.h"
+#include "srp-mdns-proxy.h"
+#include "test-dnssd.h"
+#include "test.h"
+
+static void
+test_multi_host_record_continue(test_state_t *state)
+{
+    TEST_PASSED(state);
+}
+
+void
+test_multi_host_record_start(test_state_t *next_test)
+{
+    extern srp_server_t *srp_servers;
+    const char *description =
+        "  The goal of this test is to see that when we add a service with SRP and then update the\n"
+        "  service to change its text record, the resulting state is accurately replicated to an SRP\n"
+        "  replication peer. The test succeeds if, on the peer, we see that the host and the instance\n"
+        "  refer to different records.";
+    test_state_t *state = test_state_create(srp_servers,
+                                            "Multiple Host Record Replication test", NULL, description, NULL);
+
+    state->next = next_test;
+    state->continue_testing = test_multi_host_record_continue;
+    test_change_text_record_start(state);
+
+    // Test should not take longer than ten seconds.
+    srp_test_state_add_timeout(state, 10);
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/test/tests/single-srpl-update.c b/ServiceRegistration/test/tests/single-srpl-update.c
new file mode 100644
index 0000000..a69f8d0
--- /dev/null
+++ b/ServiceRegistration/test/tests/single-srpl-update.c
@@ -0,0 +1,139 @@
+/* single-srp-update.c
+ *
+ * Copyright (c) 2023-2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains the SRP server test runner.
+ */
+
+#include "srp.h"
+#include <dns_sd.h>
+#include "srp-test-runner.h"
+#include "srp-api.h"
+#include "dns-msg.h"
+#include "ioloop.h"
+#include "srp-mdns-proxy.h"
+#include "test-api.h"
+#include "srp-proxy.h"
+#include "srp-mdns-proxy.h"
+#include "test-dnssd.h"
+#include "test.h"
+#include "srp-replication.h"
+#include "test-packet.h"
+#include "srp-proxy.h"
+#include "dns-msg.h"
+
+static void
+test_single_srpl_update_advertise_finished(test_state_t *state)
+{
+    // Check for a DNSServiceRegosterRecord for both an AAAA record and a KEY record, we don't care which order.
+    dns_service_event_t *regrec_1 =
+        dns_service_find_first_register_record_event_by_name(state->primary, TEST_HOST_NAME_REGISTERED);
+    TEST_FAIL_CHECK(state, regrec_1 != NULL, "found zero register record events");
+    regrec_1->consumed = true;
+    dns_service_event_t *regrec_2 =
+        dns_service_find_first_register_record_event_by_name(state->primary, TEST_HOST_NAME_REGISTERED);
+    TEST_FAIL_CHECK(state, regrec_1 != NULL, "found only one register record event");
+    regrec_2->consumed = true;
+    TEST_FAIL_CHECK(state,
+                    ((regrec_1->rrtype == dns_rrtype_aaaa && regrec_2->rrtype == dns_rrtype_key) ||
+                     (regrec_2->rrtype == dns_rrtype_aaaa && regrec_1->rrtype == dns_rrtype_key)),
+                    "didn't find a KEY and an AAAA record register event");
+    TEST_FAIL_CHECK(state, regrec_1->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord failed when it should have succeeded.");
+    TEST_FAIL_CHECK(state, regrec_2->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord failed when it should have succeeded.");
+
+    // Check for a DNSServiceRegister on the test service instance name and service type.
+    dns_service_event_t *reg =
+        dns_service_find_first_register_event_by_name_and_type(state->primary, TEST_INSTANCE_NAME, TEST_SERVICE_TYPE);
+    TEST_FAIL_CHECK(state, reg != NULL, "didn't find a register event");
+    TEST_FAIL_CHECK(state, reg->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegister failed when it should have succeeded.");
+    reg->consumed = true;
+
+    dns_service_event_t *rrcb_1 = dns_service_find_callback_for_registration(state->primary, regrec_1);
+    TEST_FAIL_CHECK(state, rrcb_1 != NULL, "no register record callback for first register record");
+    TEST_FAIL_CHECK(state, rrcb_1->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord callback got an error when it should not have.");
+    rrcb_1->consumed = true;
+    dns_service_event_t *rrcb_2 = dns_service_find_callback_for_registration(state->primary, regrec_2);
+    TEST_FAIL_CHECK(state, rrcb_2 != NULL, "no register record callback for second register record");
+    TEST_FAIL_CHECK(state, rrcb_2->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord callback got an error when it should not have.");
+    rrcb_2->consumed = true;
+
+    dns_service_event_t *regcb = dns_service_find_callback_for_registration(state->primary, reg);
+    TEST_FAIL_CHECK(state, regcb != NULL, "no register callback for registered service");
+    TEST_FAIL_CHECK(state, regcb->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegister callback got an error when it should not have.");
+    regcb->consumed = true;
+
+    // We should now have consumed all of the events.
+    TEST_FAIL_CHECK(state, dns_service_dump_unexpected_events(state, state->primary), "unexpected dnssd transactions remain");
+
+    // If so, the test has passed.
+    TEST_PASSED(state);
+}
+
+void
+test_single_srpl_update(test_state_t *next_test)
+{
+    extern srp_server_t *srp_servers;
+    const char *description =
+        "  The goal of this test is to generate a DNS Update message and deliver it through the SRP replication\n"
+        "  input path. We then intercept the update finished event and check the results.";
+	test_state_t *state = test_state_create(srp_servers, "Single SRPL Update test", NULL, description, NULL);
+
+    srp_proxy_init("local");
+    srp_test_enable_stub_router(state, srp_servers);
+    srp_servers->srp_replication_enabled = true;
+    state->next = next_test;
+
+	// Set up a SRP client state so that we can generate packets.
+	state->test_packet_state = test_packet_state_create(state, test_single_srpl_update_advertise_finished);
+
+	// Create a service to add to the packet.
+    DNSServiceRef ref;
+    char txt_buf[128];
+    TXTRecordRef txt;
+
+    TXTRecordCreate(&txt, sizeof(txt_buf), txt_buf);
+    TXTRecordSetValue(&txt, "foo", 1, "1");
+    TXTRecordSetValue(&txt, "bar", 3, "1.1");
+    const char *txt_data = TXTRecordGetBytesPtr(&txt);
+    int txt_len = TXTRecordGetLength(&txt);
+
+    // Create a DNSSD client
+    int ret = srp_client_register(&ref, 0 /* flags */, 0 /* interfaceIndex */,
+                                  TEST_INSTANCE_NAME /* name */, TEST_SERVICE_TYPE /* regType */,
+                                  NULL /* domain */, TEST_HOST_NAME /* host */, 1234, txt_len, txt_data,
+                                  (DNSServiceRegisterReply)1, state);
+    TEST_FAIL_CHECK_STATUS(state, ret == kDNSServiceErr_NoError, "srp_client_register returned %d", ret);
+    test_packet_generate(state, 10, 10, false, false);
+
+    test_packet_start(state, false);
+
+    // Test should not take longer than ten seconds.
+    srp_test_state_add_timeout(state, 10);
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/test/tests/srpl-cycle-through-peers.c b/ServiceRegistration/test/tests/srpl-cycle-through-peers.c
new file mode 100644
index 0000000..ef3fb6e
--- /dev/null
+++ b/ServiceRegistration/test/tests/srpl-cycle-through-peers.c
@@ -0,0 +1,290 @@
+/* srpl-cycle-through-peers.c
+ *
+ * Copyright (c) 2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains the SRP server test runner.
+ */
+
+#include "srp.h"
+#include <dns_sd.h>
+#include "srp-test-runner.h"
+#include "srp-api.h"
+#include "dns-msg.h"
+#include "ioloop.h"
+#include "srp-mdns-proxy.h"
+#include "test-api.h"
+#include "srp-proxy.h"
+#include "srp-mdns-proxy.h"
+#include "test-dnssd.h"
+#include "test.h"
+#include "srp-replication.h"
+#include "test-packet.h"
+#include "test-srpl.h"
+#include "srp-proxy.h"
+#include "dns-msg.h"
+#include "srp-tls.h"
+
+#define LEASE_TIME 40
+#define NUM_SRP_SERVERS 4
+
+static int num_srp_servers = 1;
+
+static void
+test_srpl_cycle_through_peers_evaluate(test_state_t *state, srp_server_t *server)
+{
+    // Check for a DNSServiceRegosterRecord for both an AAAA record and a KEY record, we don't care which order.
+    dns_service_event_t *regrec_1 =
+        dns_service_find_first_register_record_event_by_name(server, TEST_HOST_NAME_REGISTERED);
+    TEST_FAIL_CHECK(state, regrec_1 != NULL, "replication:found zero register record events");
+    regrec_1->consumed = true;
+    dns_service_event_t *regrec_2 =
+        dns_service_find_first_register_record_event_by_name(server, TEST_HOST_NAME_REGISTERED);
+    TEST_FAIL_CHECK(state, regrec_1 != NULL, "found only one register record event");
+    regrec_2->consumed = true;
+    TEST_FAIL_CHECK(state,
+                    ((regrec_1->rrtype == dns_rrtype_aaaa && regrec_2->rrtype == dns_rrtype_key) ||
+                     (regrec_2->rrtype == dns_rrtype_aaaa && regrec_1->rrtype == dns_rrtype_key)),
+                    "didn't find a KEY and an AAAA record register event");
+    TEST_FAIL_CHECK(state, regrec_1->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord failed when it should have succeeded.");
+    TEST_FAIL_CHECK(state, regrec_2->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord failed when it should have succeeded.");
+
+    // Check for a DNSServiceRegister on the test service instance name and service type.
+    dns_service_event_t *reg =
+        dns_service_find_first_register_event_by_name_and_type(server, TEST_INSTANCE_NAME, TEST_SERVICE_TYPE);
+    TEST_FAIL_CHECK(state, reg != NULL, "didn't find a register event");
+    TEST_FAIL_CHECK(state, reg->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegister failed when it should have succeeded.");
+    reg->consumed = true;
+
+    dns_service_event_t *rrcb_1 = dns_service_find_callback_for_registration(server, regrec_1);
+    TEST_FAIL_CHECK(state, rrcb_1 != NULL, "no register record callback for first register record");
+    TEST_FAIL_CHECK(state, rrcb_1->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord callback got an error when it should not have.");
+    rrcb_1->consumed = true;
+    dns_service_event_t *rrcb_2 = dns_service_find_callback_for_registration(server, regrec_2);
+    TEST_FAIL_CHECK(state, rrcb_2 != NULL, "no register record callback for second register record");
+    TEST_FAIL_CHECK(state, rrcb_2->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord callback got an error when it should not have.");
+    rrcb_2->consumed = true;
+
+    dns_service_event_t *regcb = dns_service_find_callback_for_registration(server, reg);
+    TEST_FAIL_CHECK(state, regcb != NULL, "no register callback for registered service");
+    TEST_FAIL_CHECK(state, regcb->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegister callback got an error when it should not have.");
+    regcb->consumed = true;
+
+    // Check for remove record
+    dns_service_event_t *remove_rec1 = dns_service_find_remove_for_register_event(server,
+                                                                                  regrec_1, NULL);
+    TEST_FAIL_CHECK(state, remove_rec1 != NULL, "found zero remove record events");
+    remove_rec1->consumed = true;
+    dns_service_event_t *remove_rec2 = dns_service_find_remove_for_register_event(server,
+                                                                                  regrec_2, NULL);
+    TEST_FAIL_CHECK(state, remove_rec2 != NULL, "found one remove record events");
+    remove_rec2->consumed = true;
+
+    dns_service_event_t *remove_instance = dns_service_find_ref_deallocate_event(server);
+    TEST_FAIL_CHECK(state, remove_instance != NULL, "found zero remove instance events");
+    remove_instance->consumed = true;
+
+    // We should now have consumed all of the events.
+    TEST_FAIL_CHECK(state, dns_service_dump_unexpected_events(state, server), "unexpected dnssd transactions remain");
+
+    // If so, the test has passed.
+    TEST_PASSED(state);
+}
+
+static void
+test_srpl_cycle_through_peers_replication_finished(srpl_connection_t *connection);
+
+static void
+test_srpl_next_srp_server(test_state_t *state, srp_server_t *prev)
+{
+    srp_server_t *new = test_srpl_add_server(state);
+    // create an outgoing connection from new server to previous
+    srpl_connection_t *connection = test_srpl_connection_create(state, prev, new);
+    INFO("create connectionn from new %p to prev %p", new, prev);
+    connection->srpl_advertise_finished_callback = test_srpl_cycle_through_peers_replication_finished;
+    // Start replication on the server side (start listener etc).
+    test_srpl_start_replication(prev, 12345 + num_srp_servers);
+    num_srp_servers++;
+    INFO("%d srp servers started", num_srp_servers);
+}
+
+static void
+test_srpl_cycle_through_peers_replication_finished(srpl_connection_t *connection)
+{
+    test_state_t *state = connection->test_state;
+    srp_server_t *client = connection->instance->domain->server_state;
+    srp_server_t *server = connection->server;
+
+    if (num_srp_servers < NUM_SRP_SERVERS) {
+        // stop listener on the server of the srpl connection
+        ioloop_comm_cancel(server->srpl_listener);
+        // after finish synchronizing, discontinue the old peer.
+        srpl_shutdown(client);
+        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 10), dispatch_get_main_queue(), ^{
+                    // add a new srp server in 10 seconds
+                    test_srpl_next_srp_server(state, client);
+        });
+    } else {
+        // if this is the last srp server, schedule to check the results in 10 seconds
+        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 10), dispatch_get_main_queue(), ^{
+                    test_srpl_cycle_through_peers_evaluate(state, client);
+        });
+    }
+}
+
+static void
+test_srpl_cycle_through_peers_primary_evaluate(test_state_t *state)
+{
+    // Check for a DNSServiceRegosterRecord for both an AAAA record and a KEY record, we don't care which order.
+    dns_service_event_t *regrec_1 =
+        dns_service_find_first_register_record_event_by_name(state->primary, TEST_HOST_NAME_REGISTERED);
+    TEST_FAIL_CHECK(state, regrec_1 != NULL, "found zero register record events");
+    regrec_1->consumed = true;
+    dns_service_event_t *regrec_2 =
+        dns_service_find_first_register_record_event_by_name(state->primary, TEST_HOST_NAME_REGISTERED);
+    TEST_FAIL_CHECK(state, regrec_1 != NULL, "found only one register record event");
+    regrec_2->consumed = true;
+    TEST_FAIL_CHECK(state,
+                    ((regrec_1->rrtype == dns_rrtype_aaaa && regrec_2->rrtype == dns_rrtype_key) ||
+                     (regrec_2->rrtype == dns_rrtype_aaaa && regrec_1->rrtype == dns_rrtype_key)),
+                    "didn't find a KEY and an AAAA record register event");
+    TEST_FAIL_CHECK(state, regrec_1->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord failed when it should have succeeded.");
+    TEST_FAIL_CHECK(state, regrec_2->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord failed when it should have succeeded.");
+
+    // Check for a DNSServiceRegister on the test service instance name and service type.
+    dns_service_event_t *reg =
+        dns_service_find_first_register_event_by_name_and_type(state->primary, TEST_INSTANCE_NAME, TEST_SERVICE_TYPE);
+    TEST_FAIL_CHECK(state, reg != NULL, "didn't find a register event");
+    TEST_FAIL_CHECK(state, reg->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegister failed when it should have succeeded.");
+    reg->consumed = true;
+
+    dns_service_event_t *rrcb_1 = dns_service_find_callback_for_registration(state->primary, regrec_1);
+    TEST_FAIL_CHECK(state, rrcb_1 != NULL, "no register record callback for first register record");
+    TEST_FAIL_CHECK(state, rrcb_1->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord callback got an error when it should not have.");
+    rrcb_1->consumed = true;
+    dns_service_event_t *rrcb_2 = dns_service_find_callback_for_registration(state->primary, regrec_2);
+    TEST_FAIL_CHECK(state, rrcb_2 != NULL, "no register record callback for second register record");
+    TEST_FAIL_CHECK(state, rrcb_2->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord callback got an error when it should not have.");
+    rrcb_2->consumed = true;
+
+    dns_service_event_t *regcb = dns_service_find_callback_for_registration(state->primary, reg);
+    TEST_FAIL_CHECK(state, regcb != NULL, "no register callback for registered service");
+    TEST_FAIL_CHECK(state, regcb->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegister callback got an error when it should not have.");
+    regcb->consumed = true;
+
+    // We should now have consumed all of the events.
+    TEST_FAIL_CHECK(state, dns_service_dump_unexpected_events(state, state->primary), "unexpected dnssd transactions remain");
+}
+
+static void
+test_srpl_cycle_through_peers_callback(DNSServiceRef sdref, DNSServiceFlags UNUSED flags, DNSServiceErrorType errorCode,
+                                       const char *name, const char *regtype, const char *UNUSED domain, void *context)
+{
+    srp_server_t *server_state = context;
+    test_state_t *state = server_state->test_state;
+
+    INFO("Register Reply for %s . %s: %d", name, regtype, errorCode);
+    INFO("state = %p", state);
+
+    if (errorCode != kDNSServiceErr_NoError) {
+        TEST_FAIL_STATUS(state, "registration failed: srp_client_register callback returned %d", errorCode);
+    }
+    // Allow time for the mDNS registration to finish
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 2), dispatch_get_main_queue(), ^{
+                // Verify the registration on the primary server.
+                test_srpl_cycle_through_peers_primary_evaluate(state);
+                void *io_context = state->current_io_context;
+                TEST_FAIL_CHECK(state, io_context != NULL, "NULL io_context");
+                srp_cancel_wakeup(server_state, io_context);// cancel the lease renewal
+                srp_client_ref_deallocate(sdref);
+                // Wait to start a new replication server.
+                dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 10), dispatch_get_main_queue(), ^{
+                            test_srpl_next_srp_server(state, state->primary);
+                });
+    });
+}
+
+static void
+test_srpl_cycle_through_peers_ready(void *context, uint16_t UNUSED port)
+{
+    srp_server_t *state = context;
+
+    int ret = srp_host_init(state);
+    srp_set_lease_times(LEASE_TIME, LEASE_TIME);
+    TEST_FAIL_CHECK(state->test_state, ret == kDNSServiceErr_NoError, "srp_host_init failed");
+
+    DNSServiceRef ref;
+    INFO("SRPL cycle through peers test");
+    char txt_buf[128];
+    TXTRecordRef txt;
+
+    TXTRecordCreate(&txt, sizeof(txt_buf), txt_buf);
+    TXTRecordSetValue(&txt, "foo", 1, "1");
+    TXTRecordSetValue(&txt, "bar", 3, "1.1");
+    const char *txt_data = TXTRecordGetBytesPtr(&txt);
+    int txt_len = TXTRecordGetLength(&txt);
+
+    // Create a DNSSD client
+    ret = srp_client_register(&ref, 0 /* flags */, 0 /* interfaceIndex */,
+                              TEST_INSTANCE_NAME /* name */, TEST_SERVICE_TYPE /* regType */,
+                              NULL /* domain */, TEST_HOST_NAME /* host */, 1234, txt_len, txt_data,
+                              test_srpl_cycle_through_peers_callback, state);
+    TEST_FAIL_CHECK_STATUS(state->test_state, ret == kDNSServiceErr_NoError, "srp_client_register returned %d", ret);
+    srp_test_network_localhost_start(state->test_state);
+}
+
+void
+test_srpl_cycle_through_peers(test_state_t *next_state)
+{
+    extern srp_server_t *srp_servers;
+    const char *description =
+        "    The goal of this test is to validate that lease time is not accidentally extended because\n"
+        "    of replication. The test first starts a single SRP server and registers a host and service\n"
+        "    with a lease time of 40 seconds. Then every 10 seconds, it starts a new SRP server and after\n"
+        "    the new server finsihes synchronization it discontinue the previous server. After the lease\n"
+        "    time, it checks that the registration is no longer present on the last SRP server.";
+    test_state_t *state = test_state_create(srp_servers, "Replication cycle through peers test", NULL, description, NULL);
+
+    srp_proxy_init("local");
+    srp_test_enable_stub_router(state, srp_servers);
+    state->primary->min_lease_time = LEASE_TIME;
+    state->srp_listener = srp_proxy_listen(NULL, 0, NULL, test_srpl_cycle_through_peers_ready, NULL, NULL, NULL, state->primary);
+    TEST_FAIL_CHECK(state, state->srp_listener != NULL, "listener create failed");
+
+    state->next = next_state;
+
+    // Test should not take longer than LEASE_TIME + 20.
+    srp_test_state_add_timeout(state, LEASE_TIME + 20);
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/test/tests/srpl-host-0i-2s.c b/ServiceRegistration/test/tests/srpl-host-0i-2s.c
new file mode 100644
index 0000000..1c6f1f7
--- /dev/null
+++ b/ServiceRegistration/test/tests/srpl-host-0i-2s.c
@@ -0,0 +1,165 @@
+/* srpl-host-0i-2s.c
+ *
+ * Copyright (c) 2023-2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains the SRP server test runner.
+ */
+
+#include "srp.h"
+#include <dns_sd.h>
+#include "srp-test-runner.h"
+#include "srp-api.h"
+#include "dns-msg.h"
+#include "ioloop.h"
+#include "srp-mdns-proxy.h"
+#include "test-api.h"
+#include "srp-proxy.h"
+#include "srp-mdns-proxy.h"
+#include "test-dnssd.h"
+#include "test.h"
+#include "srp-replication.h"
+#include "test-packet.h"
+#include "test-srpl.h"
+#include "srp-proxy.h"
+#include "dns-msg.h"
+#include "srp-tls.h"
+
+static void
+test_host_0i2s_test_finished(srpl_connection_t *connection)
+{
+    test_state_t *state = connection->test_state;
+    srp_server_t *server = connection->instance->domain->server_state;
+    // Check for a DNSServiceRegosterRecord for both an AAAA record and a KEY record, we don't care which order.
+    dns_service_event_t *regrec_1 =
+        dns_service_find_first_register_record_event_by_name(server, TEST_HOST_NAME_REGISTERED);
+    TEST_FAIL_CHECK(state, regrec_1 != NULL, "found zero register record events");
+    regrec_1->consumed = true;
+    dns_service_event_t *regrec_2 =
+        dns_service_find_first_register_record_event_by_name(server, TEST_HOST_NAME_REGISTERED);
+    TEST_FAIL_CHECK(state, regrec_1 != NULL, "found only one register record event");
+    regrec_2->consumed = true;
+    TEST_FAIL_CHECK(state,
+                    ((regrec_1->rrtype == dns_rrtype_aaaa && regrec_2->rrtype == dns_rrtype_key) ||
+                     (regrec_2->rrtype == dns_rrtype_aaaa && regrec_1->rrtype == dns_rrtype_key)),
+                    "didn't find a KEY and an AAAA record register event");
+    TEST_FAIL_CHECK(state, regrec_1->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord failed when it should have succeeded.");
+    TEST_FAIL_CHECK(state, regrec_2->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord failed when it should have succeeded.");
+
+    dns_service_event_t *rrcb_1 = dns_service_find_callback_for_registration(server, regrec_1);
+    TEST_FAIL_CHECK(state, rrcb_1 != NULL, "no register record callback for first register record");
+    TEST_FAIL_CHECK(state, rrcb_1->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord callback got an error when it should not have.");
+    rrcb_1->consumed = true;
+    dns_service_event_t *rrcb_2 = dns_service_find_callback_for_registration(server, regrec_2);
+    TEST_FAIL_CHECK(state, rrcb_2 != NULL, "no register record callback for second register record");
+    TEST_FAIL_CHECK(state, rrcb_2->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord callback got an error when it should not have.");
+    rrcb_2->consumed = true;
+
+    // We should now have consumed all of the events.
+    TEST_FAIL_CHECK(state, dns_service_dump_unexpected_events(state, server), "unexpected dnssd transactions remain");
+
+    // If so, the test has passed.
+    TEST_PASSED(state);
+}
+
+static void
+test_srpl_host_0i2s_primary_evaluate(test_state_t *state)
+{
+    // Check for a DNSServiceRegosterRecord for both an AAAA record and a KEY record, we don't care which order.
+    dns_service_event_t *regrec_1 =
+        dns_service_find_first_register_record_event_by_name(state->primary, TEST_HOST_NAME_REGISTERED);
+    TEST_FAIL_CHECK(state, regrec_1 != NULL, "found zero register record events");
+    regrec_1->consumed = true;
+    dns_service_event_t *regrec_2 =
+        dns_service_find_first_register_record_event_by_name(state->primary, TEST_HOST_NAME_REGISTERED);
+    TEST_FAIL_CHECK(state, regrec_1 != NULL, "found only one register record event");
+    regrec_2->consumed = true;
+    TEST_FAIL_CHECK(state,
+                    ((regrec_1->rrtype == dns_rrtype_aaaa && regrec_2->rrtype == dns_rrtype_key) ||
+                     (regrec_2->rrtype == dns_rrtype_aaaa && regrec_1->rrtype == dns_rrtype_key)),
+                    "didn't find a KEY and an AAAA record register event");
+    TEST_FAIL_CHECK(state, regrec_1->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord failed when it should have succeeded.");
+    TEST_FAIL_CHECK(state, regrec_2->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord failed when it should have succeeded.");
+
+    dns_service_event_t *rrcb_1 = dns_service_find_callback_for_registration(state->primary, regrec_1);
+    TEST_FAIL_CHECK(state, rrcb_1 != NULL, "no register record callback for first register record");
+    TEST_FAIL_CHECK(state, rrcb_1->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord callback got an error when it should not have.");
+    rrcb_1->consumed = true;
+    dns_service_event_t *rrcb_2 = dns_service_find_callback_for_registration(state->primary, regrec_2);
+    TEST_FAIL_CHECK(state, rrcb_2 != NULL, "no register record callback for second register record");
+    TEST_FAIL_CHECK(state, rrcb_2->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord callback got an error when it should not have.");
+    rrcb_2->consumed = true;
+
+    // We should now have consumed all of the events.
+    TEST_FAIL_CHECK(state, dns_service_dump_unexpected_events(state, state->primary), "unexpected dnssd transactions remain");
+}
+
+static void
+test_srpl_host_0i2s_ready(void *context, uint16_t UNUSED port)
+{
+    srp_server_t *state = context;
+    int ret = srp_host_init(state);
+    TEST_FAIL_CHECK(state->test_state, ret == kDNSServiceErr_NoError, "srp_host_init failed");
+    srp_set_hostname(TEST_HOST_NAME, NULL);
+    srp_test_network_localhost_start(state->test_state);
+    // Allow time for the client to register
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 2), dispatch_get_main_queue(), ^{
+        test_srpl_host_0i2s_primary_evaluate(state->test_state);
+        test_srpl_start_replication(state, 12345);
+    });
+}
+
+
+void
+test_srpl_host_0i2s(test_state_t *next_test)
+{
+    extern srp_server_t *srp_servers;
+    const char *description =
+        "  The goal of this test is to verify that a host registration with no instance doesn’t\n"
+        "  cause a problem either initially or when replicated. A SRP client first registers a\n"
+        "  host without instances with the primary SRP server. The update message is then delivered\n"
+        "  to the other server through SRPL connection and the host is then registered on that server.";
+    test_state_t *state = test_state_create(srp_servers, "SRPL zero-instance test", NULL, description, NULL);
+    srp_proxy_init("local");
+    srp_test_enable_stub_router(state, srp_servers);
+    srp_servers->srp_replication_enabled = true;
+    state->next = next_test;
+    srp_servers->server_id = 0;
+    srp_server_t *second = test_srpl_add_server(state);
+    // create an outgoing connection from secondary to primary
+    srpl_connection_t *connection = test_srpl_connection_create(state, state->primary, second);
+    connection->srpl_advertise_finished_callback = test_host_0i2s_test_finished;
+
+    state->srp_listener = srp_proxy_listen(NULL, 0, NULL, test_srpl_host_0i2s_ready, NULL, NULL, NULL, state->primary);
+    TEST_FAIL_CHECK(state, state->srp_listener != NULL, "listener create failed");
+    // Test should not take longer than ten seconds.
+    srp_test_state_add_timeout(state, 20);
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/test/tests/srpl-host-2i.c b/ServiceRegistration/test/tests/srpl-host-2i.c
new file mode 100644
index 0000000..67b7d41
--- /dev/null
+++ b/ServiceRegistration/test/tests/srpl-host-2i.c
@@ -0,0 +1,250 @@
+/* srpl-host-2i.c
+ *
+ * Copyright (c) 2023-2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Test: Single host registration, two instances, with variants
+ */
+
+#include "srp.h"
+#include <dns_sd.h>
+#include "srp-test-runner.h"
+#include "srp-api.h"
+#include "dns-msg.h"
+#include "ioloop.h"
+#include "srp-mdns-proxy.h"
+#include "test-api.h"
+#include "srp-proxy.h"
+#include "srp-mdns-proxy.h"
+#include "test-dnssd.h"
+#include "test.h"
+#include "srp-replication.h"
+#include "test-packet.h"
+#include "srp-proxy.h"
+#include "dns-msg.h"
+
+static DNSServiceRef
+test_srpl_host_2i_add_instance(test_state_t *state, int num)
+{
+	// Create a service to add to the packet.
+    DNSServiceRef ref;
+    char txt_buf[128];
+    TXTRecordRef txt;
+
+    TXTRecordCreate(&txt, sizeof(txt_buf), txt_buf);
+    TXTRecordSetValue(&txt, "blaznorf", 1, "1");
+    TXTRecordSetValue(&txt, "fnord", 3, "1.1");
+    const char *txt_data = TXTRecordGetBytesPtr(&txt);
+    int txt_len = TXTRecordGetLength(&txt);
+
+    const char *instance_name = num ? num == 1 ? TEST_INSTANCE_NAME_2 : TEST_INSTANCE_NAME_3 : TEST_INSTANCE_NAME;
+
+    // Create a DNSSD client
+    int ret = srp_client_register(&ref, 0 /* flags */, 0 /* interfaceIndex */,
+                                  instance_name /* name */, TEST_SERVICE_TYPE /* regType */,
+                                  NULL /* domain */, TEST_HOST_NAME /* host */, 1234, txt_len, txt_data,
+                                  (DNSServiceRegisterReply)1, state);
+    TEST_FAIL_CHECK_STATUS(state, ret == kDNSServiceErr_NoError, "srp_client_register returned %d", ret);
+    return ref;
+}
+
+static void
+test_srpl_host_2i_check_instance(test_state_t *state, const char *instance_name)
+{
+    dns_service_event_t *reg =
+        dns_service_find_first_register_event_by_name_and_type(state->primary, instance_name, TEST_SERVICE_TYPE);
+    TEST_FAIL_CHECK_STATUS(state, reg != NULL, "didn't find register event for " PUB_S_SRP, instance_name);
+    TEST_FAIL_CHECK_STATUS(state, reg->status == kDNSServiceErr_NoError,
+                           "DNSServiceRegister for " PUB_S_SRP " failed when it should have succeeded.", instance_name);
+    reg->consumed = true;
+
+    dns_service_event_t *regcb = dns_service_find_callback_for_registration(state->primary, reg);
+    TEST_FAIL_CHECK_STATUS(state, regcb != NULL,
+                           "no register callback for registered service " PUB_S_SRP, instance_name);
+    TEST_FAIL_CHECK_STATUS(state, regcb->status == kDNSServiceErr_NoError,
+                           "DNSServiceRegister callback for " PUB_S_SRP " got an error when it should not have.",
+                           instance_name);
+    regcb->consumed = true;
+}
+
+static void
+test_srpl_host_2i_advertise_finished(test_state_t *state)
+{
+    static bool once = false;
+    static bool twice = false;
+    // dns_service_dump_unexpected_events(state, state->primary);
+
+    if (!once) {
+        // Check for a DNSServiceRegosterRecord for both an AAAA record and a KEY record, we don't care which order.
+        dns_service_event_t *regrec_1 =
+            dns_service_find_first_register_record_event_by_name(state->primary, TEST_HOST_NAME_REGISTERED);
+        TEST_FAIL_CHECK(state, regrec_1 != NULL, "found zero register record events");
+        regrec_1->consumed = true;
+        dns_service_event_t *regrec_2 =
+            dns_service_find_first_register_record_event_by_name(state->primary, TEST_HOST_NAME_REGISTERED);
+        TEST_FAIL_CHECK(state, regrec_1 != NULL, "found only one register record event");
+        regrec_2->consumed = true;
+        TEST_FAIL_CHECK(state,
+                        ((regrec_1->rrtype == dns_rrtype_aaaa && regrec_2->rrtype == dns_rrtype_key) ||
+                         (regrec_2->rrtype == dns_rrtype_aaaa && regrec_1->rrtype == dns_rrtype_key)),
+                        "didn't find a KEY and an AAAA record register event");
+        TEST_FAIL_CHECK(state, regrec_1->status == kDNSServiceErr_NoError,
+                        "DNSServiceRegisterRecord failed when it should have succeeded.");
+        TEST_FAIL_CHECK(state, regrec_2->status == kDNSServiceErr_NoError,
+                        "DNSServiceRegisterRecord failed when it should have succeeded.");
+
+        // Check for a DNSServiceRegister on the test service instance name and service type.
+        dns_service_event_t *rrcb_1 = dns_service_find_callback_for_registration(state->primary, regrec_1);
+        TEST_FAIL_CHECK(state, rrcb_1 != NULL, "no register record callback for first register record");
+        TEST_FAIL_CHECK(state, rrcb_1->status == kDNSServiceErr_NoError,
+                        "DNSServiceRegisterRecord callback got an error when it should not have.");
+        rrcb_1->consumed = true;
+        dns_service_event_t *rrcb_2 = dns_service_find_callback_for_registration(state->primary, regrec_2);
+        TEST_FAIL_CHECK(state, rrcb_2 != NULL, "no register record callback for second register record");
+        TEST_FAIL_CHECK(state, rrcb_2->status == kDNSServiceErr_NoError,
+                        "DNSServiceRegisterRecord callback got an error when it should not have.");
+        rrcb_2->consumed = true;
+
+        // Check for the two instances.
+        test_srpl_host_2i_check_instance(state, TEST_INSTANCE_NAME);
+        test_srpl_host_2i_check_instance(state, TEST_INSTANCE_NAME_2);
+    } else if (twice && (state->variant == DUP_TEST_VARIANT_ADD_FIRST ||
+                         state->variant == DUP_TEST_VARIANT_ADD_LAST))
+    {
+        // Check for a DNSServiceRegister on the third test service instance name and service type.
+        test_srpl_host_2i_check_instance(state, TEST_INSTANCE_NAME_3);
+        twice = false;
+    }
+
+    // We should now have consumed all of the events.
+    TEST_FAIL_CHECK(state, dns_service_dump_unexpected_events(state, state->primary), "unexpected dnssd transactions remain");
+
+    if (state->variant != DUP_TEST_VARIANT_NO_DUP && !once) {
+        once = true;
+        switch(state->variant) {
+        case DUP_TEST_VARIANT_TWO_KEYS:
+        case DUP_TEST_VARIANT_BOTH:
+            break;
+        case DUP_TEST_VARIANT_FIRST:
+            test_packet_message_delete(state, 1);
+            break;
+        case DUP_TEST_VARIANT_LAST:
+            test_packet_message_delete(state, 0);
+            break;
+        case DUP_TEST_VARIANT_ADD_FIRST:
+            test_srpl_host_2i_add_instance(state, 2);
+            test_packet_generate(state, 10, 10, false, true);
+            twice = true;
+            break;
+        case DUP_TEST_VARIANT_ADD_LAST:
+            test_srpl_host_2i_add_instance(state, 2);
+            test_packet_generate(state, 10, 10, false, true);
+            twice = true;
+            break;
+        }
+        test_packet_start(state, false);
+        return;
+    }
+
+    // If so, the test has passed.
+    TEST_PASSED(state);
+}
+
+void
+test_srpl_host_2i(test_state_t *next_test, int variant)
+{
+    extern srp_server_t *srp_servers;
+    const char *variant_info = NULL;
+    const char *variant_name = NULL;
+    switch(variant) {
+    case DUP_TEST_VARIANT_TWO_KEYS:
+        variant_name = "two keys";
+        variant_info = "Test that if the two updates are signed with different keys, they are rejected.";
+        break;
+    case DUP_TEST_VARIANT_BOTH:
+        variant_name = "both";
+        variant_info = "Test that if the two updates contain the same messages, the second set are ignored.";
+        break;
+    case DUP_TEST_VARIANT_NO_DUP:
+        variant_name = "no dup";
+        variant_info = "Test without a duplicate update.";
+        break;
+    case DUP_TEST_VARIANT_FIRST:
+        variant_name = "first";
+        variant_info = "Test that if the second updates contain only the first message, it is ignored.";
+        break;
+    case DUP_TEST_VARIANT_LAST:
+        variant_name = "last";
+        variant_info = "Test that if the second updates contain only the last message, it is ignored.";
+        break;
+    case DUP_TEST_VARIANT_ADD_FIRST:
+        variant_name = "third instance first";
+        variant_info = "Test that if the second update starts with a new third instance, it is added.";
+        break;
+    case DUP_TEST_VARIANT_ADD_LAST:
+        variant_name = "third instance last";
+        variant_info = "Test that if the second update ends with a new third instance, it is added.";
+        break;
+    default:
+        TEST_FAIL_STATUS(NULL, "invalid variant: %d", variant);
+    }
+    const char *explanation =
+        "  The goal of this test is to generate two DNS Update messages, both for the \n"
+        "  same host, each containing a new registration for a different instance, and \n"
+        "  deliver it through the SRP replication input path. Having passed the update set once\n"
+        "  we then deliver it a second time to see that it is correctly ignored.";
+	test_state_t *state = test_state_create(srp_servers, "SRPL two-instance double-replication test",
+                                            variant_name, explanation, variant_info);
+
+    srp_proxy_init("local");
+    srp_test_enable_stub_router(state, srp_servers);
+    state->variant = variant;
+    srp_servers->srp_replication_enabled = true;
+    state->next = next_test;
+
+	// Set up a SRP client state so that we can generate packets.
+	state->test_packet_state = test_packet_state_create(state, test_srpl_host_2i_advertise_finished);
+
+    DNSServiceRef ref = test_srpl_host_2i_add_instance(state, 0);
+    test_packet_generate(state, 10, 10, false, false);
+
+    // This will mean that the subsequent update no longer contains the first instance. The instance is
+    // not actually deleted as a result of this.
+    srp_client_ref_deallocate(ref);
+
+    // If we want to test that updates with different keys are rejected
+    bool expect_fail = false;
+    if (variant == DUP_TEST_VARIANT_TWO_KEYS) {
+        test_packet_reset_key(state);
+        expect_fail = true;
+    }
+
+	// Create a service to add to the packet.
+    test_srpl_host_2i_add_instance(state, 1);;
+    test_packet_generate(state, 10, 10, false, false);
+
+    test_packet_start(state, expect_fail);
+
+    // Test should not take longer than ten seconds.
+    srp_test_state_add_timeout(state, 10);
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/test/tests/srpl-host-2ir.c b/ServiceRegistration/test/tests/srpl-host-2ir.c
new file mode 100644
index 0000000..5157b32
--- /dev/null
+++ b/ServiceRegistration/test/tests/srpl-host-2ir.c
@@ -0,0 +1,169 @@
+/* srpl-host-2ir.c
+ *
+ * Copyright (c) 2023-2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Test: Single host registration, two instances
+ */
+
+#include "srp.h"
+#include <dns_sd.h>
+#include "srp-test-runner.h"
+#include "srp-api.h"
+#include "dns-msg.h"
+#include "ioloop.h"
+#include "srp-mdns-proxy.h"
+#include "test-api.h"
+#include "srp-proxy.h"
+#include "srp-mdns-proxy.h"
+#include "test-dnssd.h"
+#include "test.h"
+#include "srp-replication.h"
+#include "test-packet.h"
+#include "srp-proxy.h"
+#include "dns-msg.h"
+
+static void
+test_srpl_host_2ir_advertise_finished(test_state_t *state)
+{
+    // dns_service_dump_unexpected_events(state, state->primary);
+
+    // Check for a DNSServiceRegosterRecord for both an AAAA record and a KEY record, we don't care which order.
+    dns_service_event_t *regrec_1 =
+        dns_service_find_first_register_record_event_by_name(state->primary, TEST_HOST_NAME_REGISTERED);
+    TEST_FAIL_CHECK(state, regrec_1 != NULL, "found zero register record events");
+    regrec_1->consumed = true;
+    dns_service_event_t *regrec_2 =
+        dns_service_find_first_register_record_event_by_name(state->primary, TEST_HOST_NAME_REGISTERED);
+    TEST_FAIL_CHECK(state, regrec_1 != NULL, "found only one register record event");
+    regrec_2->consumed = true;
+    TEST_FAIL_CHECK(state,
+                    ((regrec_1->rrtype == dns_rrtype_aaaa && regrec_2->rrtype == dns_rrtype_key) ||
+                     (regrec_2->rrtype == dns_rrtype_aaaa && regrec_1->rrtype == dns_rrtype_key)),
+                    "didn't find a KEY and an AAAA record register event");
+    TEST_FAIL_CHECK(state, regrec_1->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord failed when it should have succeeded.");
+    TEST_FAIL_CHECK(state, regrec_2->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord failed when it should have succeeded.");
+
+    // We shouldn't see a register for the first instance, because it was removed subsequently.
+
+    // We should see a register for the second instance.
+    dns_service_event_t *reg2 =
+        dns_service_find_first_register_event_by_name_and_type(state->primary, TEST_INSTANCE_NAME_2, TEST_SERVICE_TYPE);
+    TEST_FAIL_CHECK(state, reg2 != NULL, "didn't find second register event");
+    TEST_FAIL_CHECK(state, reg2->status == kDNSServiceErr_NoError,
+                    "second DNSServiceRegister failed when it should have succeeded.");
+    reg2->consumed = true;
+
+    dns_service_event_t *rrcb_1 = dns_service_find_callback_for_registration(state->primary, regrec_1);
+    TEST_FAIL_CHECK(state, rrcb_1 != NULL, "no register record callback for first register record");
+    TEST_FAIL_CHECK(state, rrcb_1->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord callback got an error when it should not have.");
+    rrcb_1->consumed = true;
+    dns_service_event_t *rrcb_2 = dns_service_find_callback_for_registration(state->primary, regrec_2);
+    TEST_FAIL_CHECK(state, rrcb_2 != NULL, "no register record callback for second register record");
+    TEST_FAIL_CHECK(state, rrcb_2->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord callback got an error when it should not have.");
+    rrcb_2->consumed = true;
+
+    dns_service_event_t *regcb2 = dns_service_find_callback_for_registration(state->primary, reg2);
+    TEST_FAIL_CHECK(state, regcb2 != NULL, "no register callback for second registered service");
+    TEST_FAIL_CHECK(state, regcb2->status == kDNSServiceErr_NoError,
+                    "second DNSServiceRegister callback got an error when it should not have.");
+    regcb2->consumed = true;
+
+    // We should now have consumed all of the events.
+    TEST_FAIL_CHECK(state, dns_service_dump_unexpected_events(state, state->primary), "unexpected dnssd transactions remain");
+
+    // If so, the test has passed.
+    TEST_PASSED(state);
+}
+
+void
+test_srpl_host_2ir(test_state_t *next_test)
+{
+    extern srp_server_t *srp_servers;
+    const char *description =
+        "  The goal of this test is to generate two DNS Update messages, both for the \n"
+        "  same host. The first contains two instances. The second contains a remove for\n"
+        "  one of the instances. The test then delivers both messages through the SRP\n"
+        "  replication input path. We then intercept the update finished event and check\n"
+        "  the results.";
+
+    test_state_t *state = test_state_create(srp_servers, "SRPL two instance add, one instance remove test",
+                                            NULL, description, NULL);
+    srp_proxy_init("local");
+    srp_test_enable_stub_router(state, srp_servers);
+    srp_servers->srp_replication_enabled = true;
+    state->next = next_test;
+
+	// Set up a SRP client state so that we can generate packets.
+	state->test_packet_state = test_packet_state_create(state, test_srpl_host_2ir_advertise_finished);
+
+	// Create a service to add to the packet.
+    DNSServiceRef ref;
+    char txt_buf[128];
+    TXTRecordRef txt;
+
+    TXTRecordCreate(&txt, sizeof(txt_buf), txt_buf);
+    TXTRecordSetValue(&txt, "foo", 1, "1");
+    TXTRecordSetValue(&txt, "bar", 3, "1.1");
+    const char *txt_data = TXTRecordGetBytesPtr(&txt);
+    int txt_len = TXTRecordGetLength(&txt);
+
+    // Create a DNSSD client
+    int ret = srp_client_register(&ref, 0 /* flags */, 0 /* interfaceIndex */,
+                                  TEST_INSTANCE_NAME /* name */, TEST_SERVICE_TYPE /* regType */,
+                                  NULL /* domain */, TEST_HOST_NAME /* host */, 1234, txt_len, txt_data,
+                                  (DNSServiceRegisterReply)1, state);
+    TEST_FAIL_CHECK_STATUS(state, ret == kDNSServiceErr_NoError, "srp_client_register returned %d", ret);
+
+	// Create a service to add to the packet.
+    DNSServiceRef ref2;
+    char txt_buf2[128];
+    TXTRecordRef txt2;
+
+    TXTRecordCreate(&txt2, sizeof(txt_buf2), txt_buf2);
+    TXTRecordSetValue(&txt2, "blaznorf", 1, "1");
+    TXTRecordSetValue(&txt2, "fnord", 3, "1.1");
+    const char *txt_data2 = TXTRecordGetBytesPtr(&txt2);
+    int txt_len2 = TXTRecordGetLength(&txt2);
+
+    // Create a DNSSD client
+    ret = srp_client_register(&ref2, 0 /* flags */, 0 /* interfaceIndex */,
+                              TEST_INSTANCE_NAME_2 /* name */, TEST_SERVICE_TYPE /* regType */,
+                              NULL /* domain */, TEST_HOST_NAME /* host */, 1234, txt_len2, txt_data2,
+                              (DNSServiceRegisterReply)1, state);
+    TEST_FAIL_CHECK_STATUS(state, ret == kDNSServiceErr_NoError, "srp_client_register returned %d", ret);
+    test_packet_generate(state, 10, 10, false, false);
+
+	// Now remove a single instance.
+	srp_deregister_instance(ref);
+    test_packet_generate(state, 10, 10, false, false);
+
+    test_packet_start(state, false);
+
+    // Test should not take longer than ten seconds.
+    srp_test_state_add_timeout(state, 10);
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/test/tests/srpl-lease-time.c b/ServiceRegistration/test/tests/srpl-lease-time.c
new file mode 100644
index 0000000..cad2807
--- /dev/null
+++ b/ServiceRegistration/test/tests/srpl-lease-time.c
@@ -0,0 +1,258 @@
+/* srpl-lease-time.c
+ *
+ * Copyright (c) 2023-2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains the SRP server test runner.
+ */
+
+#include "srp.h"
+#include <dns_sd.h>
+#include "srp-test-runner.h"
+#include "srp-api.h"
+#include "dns-msg.h"
+#include "ioloop.h"
+#include "srp-mdns-proxy.h"
+#include "test-api.h"
+#include "srp-proxy.h"
+#include "srp-mdns-proxy.h"
+#include "test-dnssd.h"
+#include "test.h"
+#include "srp-replication.h"
+#include "test-packet.h"
+#include "test-srpl.h"
+#include "srp-proxy.h"
+#include "dns-msg.h"
+#include "srp-tls.h"
+
+#define LEASE_TIME 15
+
+static void
+test_srpl_lease_time_replication_evaluate(test_state_t *state, srp_server_t *server)
+{
+    // Check for a DNSServiceRegosterRecord for both an AAAA record and a KEY record, we don't care which order.
+    dns_service_event_t *regrec_1 =
+        dns_service_find_first_register_record_event_by_name(server, TEST_HOST_NAME_REGISTERED);
+    TEST_FAIL_CHECK(state, regrec_1 != NULL, "replication:found zero register record events");
+    regrec_1->consumed = true;
+    dns_service_event_t *regrec_2 =
+        dns_service_find_first_register_record_event_by_name(server, TEST_HOST_NAME_REGISTERED);
+    TEST_FAIL_CHECK(state, regrec_1 != NULL, "found only one register record event");
+    regrec_2->consumed = true;
+    TEST_FAIL_CHECK(state,
+                    ((regrec_1->rrtype == dns_rrtype_aaaa && regrec_2->rrtype == dns_rrtype_key) ||
+                     (regrec_2->rrtype == dns_rrtype_aaaa && regrec_1->rrtype == dns_rrtype_key)),
+                    "didn't find a KEY and an AAAA record register event");
+    TEST_FAIL_CHECK(state, regrec_1->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord failed when it should have succeeded.");
+    TEST_FAIL_CHECK(state, regrec_2->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord failed when it should have succeeded.");
+
+    // Check for a DNSServiceRegister on the test service instance name and service type.
+    dns_service_event_t *reg =
+        dns_service_find_first_register_event_by_name_and_type(server, TEST_INSTANCE_NAME, TEST_SERVICE_TYPE);
+    TEST_FAIL_CHECK(state, reg != NULL, "didn't find a register event");
+    TEST_FAIL_CHECK(state, reg->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegister failed when it should have succeeded.");
+    reg->consumed = true;
+
+    dns_service_event_t *rrcb_1 = dns_service_find_callback_for_registration(server, regrec_1);
+    TEST_FAIL_CHECK(state, rrcb_1 != NULL, "no register record callback for first register record");
+    TEST_FAIL_CHECK(state, rrcb_1->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord callback got an error when it should not have.");
+    rrcb_1->consumed = true;
+    dns_service_event_t *rrcb_2 = dns_service_find_callback_for_registration(server, regrec_2);
+    TEST_FAIL_CHECK(state, rrcb_2 != NULL, "no register record callback for second register record");
+    TEST_FAIL_CHECK(state, rrcb_2->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord callback got an error when it should not have.");
+    rrcb_2->consumed = true;
+
+    dns_service_event_t *regcb = dns_service_find_callback_for_registration(server, reg);
+    TEST_FAIL_CHECK(state, regcb != NULL, "no register callback for registered service");
+    TEST_FAIL_CHECK(state, regcb->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegister callback got an error when it should not have.");
+    regcb->consumed = true;
+
+    // We should now have consumed all of the events.
+    TEST_FAIL_CHECK(state, dns_service_dump_unexpected_events(state, server), "unexpected dnssd transactions remain");
+
+    // If so, the test has passed.
+    TEST_PASSED(state);
+}
+
+static void
+test_srpl_lease_time_replication_finished(srpl_connection_t *connection)
+{
+    test_state_t *state = connection->test_state;
+    srp_server_t *server = connection->instance->domain->server_state;
+
+    INFO("reach test_srpl_lease_time_replication_finished");
+    // Wait for another LEASE_TIME to verify that the registration has the updated lease time
+    // and does not expire immediately.
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * LEASE_TIME), dispatch_get_main_queue(), ^{
+        test_srpl_lease_time_replication_evaluate(state, server);
+    });
+}
+
+static void
+test_srpl_lease_time_primary_evaluate(test_state_t *state)
+{
+    // Check for a DNSServiceRegosterRecord for both an AAAA record and a KEY record, we don't care which order.
+    dns_service_event_t *regrec_1 =
+        dns_service_find_first_register_record_event_by_name(state->primary, TEST_HOST_NAME_REGISTERED);
+    TEST_FAIL_CHECK(state, regrec_1 != NULL, "found zero register record events");
+    regrec_1->consumed = true;
+    dns_service_event_t *regrec_2 =
+        dns_service_find_first_register_record_event_by_name(state->primary, TEST_HOST_NAME_REGISTERED);
+    TEST_FAIL_CHECK(state, regrec_1 != NULL, "found only one register record event");
+    regrec_2->consumed = true;
+    TEST_FAIL_CHECK(state,
+                    ((regrec_1->rrtype == dns_rrtype_aaaa && regrec_2->rrtype == dns_rrtype_key) ||
+                     (regrec_2->rrtype == dns_rrtype_aaaa && regrec_1->rrtype == dns_rrtype_key)),
+                    "didn't find a KEY and an AAAA record register event");
+    TEST_FAIL_CHECK(state, regrec_1->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord failed when it should have succeeded.");
+    TEST_FAIL_CHECK(state, regrec_2->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord failed when it should have succeeded.");
+
+    // Check for a DNSServiceRegister on the test service instance name and service type.
+    dns_service_event_t *reg =
+        dns_service_find_first_register_event_by_name_and_type(state->primary, TEST_INSTANCE_NAME, TEST_SERVICE_TYPE);
+    TEST_FAIL_CHECK(state, reg != NULL, "didn't find a register event");
+    TEST_FAIL_CHECK(state, reg->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegister failed when it should have succeeded.");
+    reg->consumed = true;
+
+    dns_service_event_t *rrcb_1 = dns_service_find_callback_for_registration(state->primary, regrec_1);
+    TEST_FAIL_CHECK(state, rrcb_1 != NULL, "no register record callback for first register record");
+    TEST_FAIL_CHECK(state, rrcb_1->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord callback got an error when it should not have.");
+    rrcb_1->consumed = true;
+    dns_service_event_t *rrcb_2 = dns_service_find_callback_for_registration(state->primary, regrec_2);
+    TEST_FAIL_CHECK(state, rrcb_2 != NULL, "no register record callback for second register record");
+    TEST_FAIL_CHECK(state, rrcb_2->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord callback got an error when it should not have.");
+    rrcb_2->consumed = true;
+
+    dns_service_event_t *regcb = dns_service_find_callback_for_registration(state->primary, reg);
+    TEST_FAIL_CHECK(state, regcb != NULL, "no register callback for registered service");
+    TEST_FAIL_CHECK(state, regcb->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegister callback got an error when it should not have.");
+    regcb->consumed = true;
+
+    // We should now have consumed all of the events.
+    TEST_FAIL_CHECK(state, dns_service_dump_unexpected_events(state, state->primary), "unexpected dnssd transactions remain");
+}
+
+static void
+test_srpl_lease_time_callback(DNSServiceRef sdref, DNSServiceFlags UNUSED flags, DNSServiceErrorType errorCode,
+                              const char *name, const char *regtype, const char *UNUSED domain, void *context)
+{
+    srp_server_t *server_state = context;
+    test_state_t *state = server_state->test_state;
+    static bool initial = true;
+
+    INFO("Register Reply for %s . %s: %d", name, regtype, errorCode);
+    INFO("state = %p", state);
+
+    if (errorCode != kDNSServiceErr_NoError) {
+        TEST_FAIL_STATUS(state, "registration failed: srp_client_register callback returned %d", errorCode);
+    }
+    // We only check the registration on the primary once and start the replication then.
+    if (initial) {
+        // Allow time for the mDNS registration to finish
+        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 2), dispatch_get_main_queue(), ^{
+                    // Verify the registration on the primary server.
+                    test_srpl_lease_time_primary_evaluate(state);
+                    // Change the lease time to 3600 seconds
+                    srp_set_lease_times(3600, 3600);
+                    // Wait for client to renew.
+                    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * LEASE_TIME), dispatch_get_main_queue(), ^{
+                                // After client renews, start another replication peer.
+                                srp_server_t *second = test_srpl_add_server(state);
+                                // create an outgoing connection from second to primary
+                                srpl_connection_t *connection = test_srpl_connection_create(state, state->primary, second);
+                                connection->srpl_advertise_finished_callback = test_srpl_lease_time_replication_finished;
+                                // Start replication.
+                                test_srpl_start_replication(state->primary, 12345);
+                                srp_client_ref_deallocate(sdref);
+                                srp_network_state_stable(NULL); // Discontinue SRP client
+                    });
+        });
+        initial = false;
+    }
+}
+
+static void
+test_srpl_lease_time_ready(void *context, uint16_t UNUSED port)
+{
+    srp_server_t *state = context;
+
+    int ret = srp_host_init(state);
+    srp_set_lease_times(LEASE_TIME, LEASE_TIME);
+    TEST_FAIL_CHECK(state->test_state, ret == kDNSServiceErr_NoError, "srp_host_init failed");
+
+    DNSServiceRef ref;
+    INFO("SRPL lease time test");
+    char txt_buf[128];
+    TXTRecordRef txt;
+
+    TXTRecordCreate(&txt, sizeof(txt_buf), txt_buf);
+    TXTRecordSetValue(&txt, "foo", 1, "1");
+    TXTRecordSetValue(&txt, "bar", 3, "1.1");
+    const char *txt_data = TXTRecordGetBytesPtr(&txt);
+    int txt_len = TXTRecordGetLength(&txt);
+
+    // Create a DNSSD client
+    ret = srp_client_register(&ref, 0 /* flags */, 0 /* interfaceIndex */,
+                              TEST_INSTANCE_NAME /* name */, TEST_SERVICE_TYPE /* regType */,
+                              NULL /* domain */, TEST_HOST_NAME /* host */, 1234, txt_len, txt_data,
+                              test_srpl_lease_time_callback, state);
+    TEST_FAIL_CHECK_STATUS(state->test_state, ret == kDNSServiceErr_NoError, "srp_client_register returned %d", ret);
+    srp_test_network_localhost_start(state->test_state);
+}
+
+void
+test_srpl_lease_time(test_state_t *next_state)
+{
+    extern srp_server_t *srp_servers;
+    const char *description =
+        "    The goal of this test is to validate that lease time is processed correctly for a renewal.\n"
+        "    The test first sets the lease time to 15 seconds and let the client register a host and\n"
+        "    service with a srp server. It then updates the lease time on the client to 3600 seconds\n"
+        "    and wait for the client to renew. It then starts a second SRP replication server and wait\n"
+        "    for the synchronization to complete. It checks that the lease on the replicated peer does\n"
+        "    not expire immediately.";
+    test_state_t *state = test_state_create(srp_servers, "Replication lease time test", NULL, description, NULL);
+
+    srp_proxy_init("local");
+    srp_test_enable_stub_router(state, srp_servers);
+    state->primary->min_lease_time = LEASE_TIME;
+    state->srp_listener = srp_proxy_listen(NULL, 0, NULL, test_srpl_lease_time_ready, NULL, NULL, NULL, state->primary);
+    TEST_FAIL_CHECK(state, state->srp_listener != NULL, "listener create failed");
+
+    state->next = next_state;
+
+    // Test should not take longer than 3*LEASE_TIME.
+    srp_test_state_add_timeout(state, 3 * LEASE_TIME);
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/test/tests/srpl-update-after-remove.c b/ServiceRegistration/test/tests/srpl-update-after-remove.c
new file mode 100644
index 0000000..b3f66fa
--- /dev/null
+++ b/ServiceRegistration/test/tests/srpl-update-after-remove.c
@@ -0,0 +1,280 @@
+/* srpl-update-after-remove.c
+ *
+ * Copyright (c) 2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains the SRP server test runner.
+ */
+
+#include "srp.h"
+#include <dns_sd.h>
+#include "srp-test-runner.h"
+#include "srp-api.h"
+#include "dns-msg.h"
+#include "ioloop.h"
+#include "srp-mdns-proxy.h"
+#include "test-api.h"
+#include "srp-proxy.h"
+#include "srp-mdns-proxy.h"
+#include "test-dnssd.h"
+#include "test.h"
+#include "srp-replication.h"
+#include "test-packet.h"
+#include "test-srpl.h"
+#include "srp-proxy.h"
+#include "dns-msg.h"
+#include "srp-tls.h"
+
+#define LEASE_TIME 300
+static bool remove_host = false;
+
+static void
+test_srpl_update_after_remove_replication_evaluate(test_state_t *state, srp_server_t *server)
+{
+    // Check for a DNSServiceRegosterRecord for both an AAAA record and a KEY record, we don't care which order.
+    dns_service_event_t *regrec_1 =
+        dns_service_find_first_register_record_event_by_name(server, TEST_HOST_NAME_REGISTERED);
+    TEST_FAIL_CHECK(state, regrec_1 != NULL, "replication:found zero register record events");
+    regrec_1->consumed = true;
+    dns_service_event_t *regrec_2 =
+        dns_service_find_first_register_record_event_by_name(server, TEST_HOST_NAME_REGISTERED);
+    TEST_FAIL_CHECK(state, regrec_1 != NULL, "found only one register record event");
+    regrec_2->consumed = true;
+    TEST_FAIL_CHECK(state,
+                    ((regrec_1->rrtype == dns_rrtype_aaaa && regrec_2->rrtype == dns_rrtype_key) ||
+                     (regrec_2->rrtype == dns_rrtype_aaaa && regrec_1->rrtype == dns_rrtype_key)),
+                    "didn't find a KEY and an AAAA record register event");
+    TEST_FAIL_CHECK(state, regrec_1->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord failed when it should have succeeded.");
+    TEST_FAIL_CHECK(state, regrec_2->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord failed when it should have succeeded.");
+
+    // Check for a DNSServiceRegister on the test service instance name and service type.
+    dns_service_event_t *reg =
+        dns_service_find_first_register_event_by_name_and_type(server, TEST_INSTANCE_NAME, TEST_SERVICE_TYPE);
+    TEST_FAIL_CHECK(state, reg != NULL, "didn't find a register event");
+    TEST_FAIL_CHECK(state, reg->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegister failed when it should have succeeded.");
+    reg->consumed = true;
+
+    dns_service_event_t *rrcb_1 = dns_service_find_callback_for_registration(server, regrec_1);
+    TEST_FAIL_CHECK(state, rrcb_1 != NULL, "no register record callback for first register record");
+    TEST_FAIL_CHECK(state, rrcb_1->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord callback got an error when it should not have.");
+    rrcb_1->consumed = true;
+    dns_service_event_t *rrcb_2 = dns_service_find_callback_for_registration(server, regrec_2);
+    TEST_FAIL_CHECK(state, rrcb_2 != NULL, "no register record callback for second register record");
+    TEST_FAIL_CHECK(state, rrcb_2->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord callback got an error when it should not have.");
+    rrcb_2->consumed = true;
+
+    dns_service_event_t *regcb = dns_service_find_callback_for_registration(server, reg);
+    TEST_FAIL_CHECK(state, regcb != NULL, "no register callback for registered service");
+    TEST_FAIL_CHECK(state, regcb->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegister callback got an error when it should not have.");
+    regcb->consumed = true;
+
+    // We should now have consumed all of the events.
+    TEST_FAIL_CHECK(state, dns_service_dump_unexpected_events(state, server), "unexpected dnssd transactions remain");
+}
+
+static void
+test_srpl_update_after_remove_evaluate(test_state_t *state, srp_server_t *server)
+{
+    // Check for a DNSServiceRegosterRecord for both an AAAA record and a KEY record, we don't care which order.
+    dns_service_event_t *regrec_1 =
+        dns_service_find_first_register_record_event_by_name(server, TEST_HOST_NAME_REGISTERED);
+    TEST_FAIL_CHECK(state, regrec_1 != NULL, "found zero register record events");
+    regrec_1->consumed = true;
+    dns_service_event_t *regrec_2 =
+        dns_service_find_first_register_record_event_by_name(server, TEST_HOST_NAME_REGISTERED);
+    TEST_FAIL_CHECK(state, regrec_1 != NULL, "found only one register record event");
+    regrec_2->consumed = true;
+    TEST_FAIL_CHECK(state,
+                    ((regrec_1->rrtype == dns_rrtype_aaaa && regrec_2->rrtype == dns_rrtype_key) ||
+                     (regrec_2->rrtype == dns_rrtype_aaaa && regrec_1->rrtype == dns_rrtype_key)),
+                    "didn't find a KEY and an AAAA record register event");
+    TEST_FAIL_CHECK(state, regrec_1->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord failed when it should have succeeded.");
+    TEST_FAIL_CHECK(state, regrec_2->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord failed when it should have succeeded.");
+
+    // Check for a DNSServiceRegister on the test service instance name and service type.
+    dns_service_event_t *reg =
+        dns_service_find_first_register_event_by_name_and_type(server, TEST_INSTANCE_NAME, TEST_SERVICE_TYPE);
+    TEST_FAIL_CHECK(state, reg != NULL, "didn't find a register event");
+    TEST_FAIL_CHECK(state, reg->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegister failed when it should have succeeded.");
+    reg->consumed = true;
+
+    dns_service_event_t *rrcb_1 = dns_service_find_callback_for_registration(server, regrec_1);
+    TEST_FAIL_CHECK(state, rrcb_1 != NULL, "no register record callback for first register record");
+    TEST_FAIL_CHECK(state, rrcb_1->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord callback got an error when it should not have.");
+    rrcb_1->consumed = true;
+    dns_service_event_t *rrcb_2 = dns_service_find_callback_for_registration(server, regrec_2);
+    TEST_FAIL_CHECK(state, rrcb_2 != NULL, "no register record callback for second register record");
+    TEST_FAIL_CHECK(state, rrcb_2->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegisterRecord callback got an error when it should not have.");
+    rrcb_2->consumed = true;
+
+    dns_service_event_t *regcb = dns_service_find_callback_for_registration(server, reg);
+    TEST_FAIL_CHECK(state, regcb != NULL, "no register callback for registered service");
+    TEST_FAIL_CHECK(state, regcb->status == kDNSServiceErr_NoError,
+                    "DNSServiceRegister callback got an error when it should not have.");
+    regcb->consumed = true;
+
+    // Check for remove record
+    dns_service_event_t *remove_rec1 = dns_service_find_remove_for_register_event(server,
+                                                                                      regrec_1, NULL);
+    TEST_FAIL_CHECK(state, remove_rec1 != NULL, "found zero remove record events");
+    remove_rec1->consumed = true;
+    dns_service_event_t *remove_rec2 = dns_service_find_remove_for_register_event(server,
+                                                                                      regrec_2, NULL);
+    TEST_FAIL_CHECK(state, remove_rec2 != NULL, "found one remove record events");
+    remove_rec2->consumed = true;
+
+    dns_service_event_t *remove_instance = dns_service_find_ref_deallocate_event(server);
+    TEST_FAIL_CHECK(state, remove_instance != NULL, "found zero remove instance events");
+    remove_instance->consumed = true;
+
+    // We should now have consumed all of the events.
+    TEST_FAIL_CHECK(state, dns_service_dump_unexpected_events(state, server), "unexpected dnssd transactions remain");
+
+    // If so, the test has passed.
+    TEST_PASSED(state);
+}
+
+static void
+test_srpl_update_after_remove_replication_finished(srpl_connection_t *connection)
+{
+    test_state_t *state = connection->test_state;
+    srp_server_t *client = connection->instance->domain->server_state;
+    srp_server_t *server = connection->server;
+
+    INFO("reach test_srpl_update_after_remove_replication_finished");
+    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 2), dispatch_get_main_queue(), ^{
+            test_srpl_update_after_remove_replication_evaluate(state, client);
+            // client to remove the host
+            remove_host = true;
+            srp_deregister(server);
+            void *io_context = state->current_io_context;
+            TEST_FAIL_CHECK(state, io_context != NULL, "NULL io_context");
+            srp_cancel_wakeup(server, io_context);// cancel any retransmit
+            // trigger disconnect on the primary and make client to reconnect
+            srpl_drop_srpl_connection(server);
+            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 5), dispatch_get_main_queue(), ^{
+                    test_srpl_update_after_remove_evaluate(state, server);
+            });
+    });
+}
+
+static void
+test_srpl_update_after_remove_callback(DNSServiceRef UNUSED sdref, DNSServiceFlags UNUSED flags, DNSServiceErrorType errorCode,
+                              const char *name, const char *regtype, const char *UNUSED domain, void *context)
+{
+    srp_server_t *server_state = context;
+    test_state_t *state = server_state->test_state;
+    static bool initial_register = true;
+
+    INFO("Register Reply for %s . %s: %d", name, regtype, errorCode);
+    INFO("state = %p", state);
+
+    if (errorCode != kDNSServiceErr_NoError) {
+        if (remove_host && errorCode == kDNSServiceErr_NoSuchRecord) {
+            return;
+        }
+        TEST_FAIL_STATUS(state, "registration failed: srp_client_register callback returned %d", errorCode);
+    }
+
+    if (initial_register) {
+        // We check the registration on the primary and start the replication then.
+        // Allow time for the mDNS registration to finish
+        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 2), dispatch_get_main_queue(), ^{
+                    // Verify the registration on the primary server.
+                    //test_srpl_update_after_remove_evaluate(state, server_state, false);
+                    // start another replication peer.
+                    srp_server_t *second = test_srpl_add_server(state);
+                    // create an outgoing connection from second to primary
+                    srpl_connection_t *connection = test_srpl_connection_create(state, state->primary, second);
+                    connection->srpl_advertise_finished_callback = test_srpl_update_after_remove_replication_finished;
+                    // Start replication on the server.
+                    test_srpl_start_replication(state->primary, 12345);
+        });
+        initial_register = false;
+    }
+}
+
+static void
+test_srpl_update_after_remove_ready(void *context, uint16_t UNUSED port)
+{
+    srp_server_t *state = context;
+
+    int ret = srp_host_init(state);
+    srp_set_lease_times(LEASE_TIME, LEASE_TIME);
+    TEST_FAIL_CHECK(state->test_state, ret == kDNSServiceErr_NoError, "srp_host_init failed");
+
+    DNSServiceRef ref;
+    char txt_buf[128];
+    TXTRecordRef txt;
+
+    TXTRecordCreate(&txt, sizeof(txt_buf), txt_buf);
+    TXTRecordSetValue(&txt, "foo", 1, "1");
+    TXTRecordSetValue(&txt, "bar", 3, "1.1");
+    const char *txt_data = TXTRecordGetBytesPtr(&txt);
+    int txt_len = TXTRecordGetLength(&txt);
+
+    // Create a DNSSD client
+    ret = srp_client_register(&ref, 0 /* flags */, 0 /* interfaceIndex */,
+                              TEST_INSTANCE_NAME /* name */, TEST_SERVICE_TYPE /* regType */,
+                              NULL /* domain */, TEST_HOST_NAME /* host */, 1234, txt_len, txt_data,
+                              test_srpl_update_after_remove_callback, state);
+    TEST_FAIL_CHECK_STATUS(state->test_state, ret == kDNSServiceErr_NoError, "srp_client_register returned %d", ret);
+    srp_test_network_localhost_start(state->test_state);
+}
+
+void
+test_srpl_update_after_remove(test_state_t *next_state)
+{
+    extern srp_server_t *srp_servers;
+    const char *description =
+        "    The goal of this test is to validate that a stale update through replication does not\n"
+        "    re-add the host that has been removed by the actual device. The test first lets the\n"
+        "    client register a host and service with a srp server. After the registration is\n"
+        "    replicated to the second srp server, it then deletes the registered host and disconnects\n"
+        "    the srpl connection. The second srp server reconnects and send srp update to the\n"
+        "    first srp server. It then checks that the srp update does not re-add the host on\n"
+        "    the first srp server.";
+    test_state_t *state = test_state_create(srp_servers, "Replication update after remove test", NULL, description, NULL);
+
+    srp_proxy_init("local");
+    srp_servers->srp_replication_enabled = true;
+    srp_test_enable_stub_router(state, srp_servers);
+    state->primary->min_lease_time = LEASE_TIME;
+    state->srp_listener = srp_proxy_listen(NULL, 0, NULL, test_srpl_update_after_remove_ready, NULL, NULL, NULL, state->primary);
+    TEST_FAIL_CHECK(state, state->srp_listener != NULL, "listener create failed");
+
+    state->next = next_state;
+
+    srp_test_state_add_timeout(state, 40);
+}
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/test/tests/test.h b/ServiceRegistration/test/tests/test.h
new file mode 100644
index 0000000..c371b42
--- /dev/null
+++ b/ServiceRegistration/test/tests/test.h
@@ -0,0 +1,66 @@
+/* test.h
+ *
+ * Copyright (c) 2023-2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * This file contains the SRP server test runner.
+ */
+
+#define TEST_INSTANCE_NAME_3      "third-instance"
+#define TEST_INSTANCE_NAME_2      "second-instance"
+#define TEST_INSTANCE_NAME        "test-client"
+#define TEST_SERVICE_TYPE         "_srp-test._udp"
+#define TEST_HOST_NAME            "test-client"
+#define TEST_HOST_NAME_REGISTERED "test-client.local."
+
+#define DUP_TEST_VARIANT_BOTH      1
+#define DUP_TEST_VARIANT_FIRST     2
+#define DUP_TEST_VARIANT_LAST      3
+#define DUP_TEST_VARIANT_ADD_FIRST 4
+#define DUP_TEST_VARIANT_ADD_LAST  5
+#define DUP_TEST_VARIANT_TWO_KEYS  6
+#define DUP_TEST_VARIANT_NO_DUP    7
+
+#define PUSH_TEST_VARIANT_HARDWIRED     1
+#define PUSH_TEST_VARIANT_MDNS          2
+#define PUSH_TEST_VARIANT_DAEMON_CRASH  3
+#define PUSH_TEST_VARIANT_DNS_HARDWIRED 4
+#define PUSH_TEST_VARIANT_DNS_MDNS      5
+#define PUSH_TEST_VARIANT_DNS_CRASH     6
+#define PUSH_TEST_VARIANT_TWO_QUESTIONS 7
+
+void test_change_text_record_start(test_state_t *NULLABLE next_state);
+void test_lease_expiry_start(test_state_t *NULLABLE next_state);
+void test_lease_renewal_start(test_state_t *NULLABLE next_state);
+void test_multi_host_record_start(test_state_t *NULLABLE next_state);
+void test_single_srpl_update(test_state_t *NULLABLE next_test);
+void test_srpl_host_2i(test_state_t *NULLABLE next_test, int variant);
+void test_srpl_host_2ir(test_state_t *NULLABLE next_test);
+void test_srpl_host_0i2s(test_state_t *NULLABLE next_test);
+void test_srpl_lease_time(test_state_t *NULLABLE next_state);
+void test_dns_dangling_query(test_state_t *NULLABLE next_state);
+void test_srpl_cycle_through_peers(test_state_t *NULLABLE next_state);
+void test_srpl_update_after_remove(test_state_t *NULLABLE next_state);
+void test_dns_push(test_state_t *NULLABLE next_state, int variant);
+void test_listen_longevity_start(test_state_t *NULLABLE next_state);
+void test_ifaddrs_start(test_state_t *NULLABLE next_state);
+
+// Local Variables:
+// mode: C
+// tab-width: 4
+// c-file-style: "bsd"
+// c-basic-offset: 4
+// fill-column: 108
+// indent-tabs-mode: nil
+// End:
diff --git a/ServiceRegistration/thread-device.c b/ServiceRegistration/thread-device.c
index 334fac1..ab4e02c 100644
--- a/ServiceRegistration/thread-device.c
+++ b/ServiceRegistration/thread-device.c
@@ -1,6 +1,6 @@
 /* thread-device.c
  *
- * Copyright (c) 2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2023-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -34,6 +34,7 @@
 #include <inttypes.h>
 #include <sys/resource.h>
 #include <netinet/icmp6.h>
+#include <os/feature_private.h>
 #include "srp.h"
 #include "dns-msg.h"
 #include "srp-crypto.h"
@@ -65,11 +66,14 @@
         server_state->rloc16 = rloc16;
         INFO("server_state->rloc16 updated to %d", server_state->rloc16);
 
+        server_state->srp_on_demand = os_feature_enabled(mDNSResponder, srp_on_demand);
+        INFO("srp on demand is " PUB_S_SRP, server_state->srp_on_demand ? "enabled" : "disabled");
+
         // Now we can start.
         if (server_state->service_tracker == NULL) {
             server_state->service_tracker = service_tracker_create(server_state);
             if (server_state->service_tracker == NULL) {
-                FAULT("can't start service tracker.");
+                FAULT("can't create service tracker.");
                 return;
             }
             start = true;
@@ -77,7 +81,7 @@
         if (server_state->thread_tracker == NULL) {
             server_state->thread_tracker = thread_tracker_create(server_state);
             if (server_state->thread_tracker == NULL) {
-                FAULT("can't start thread tracker.");
+                FAULT("can't create thread tracker.");
                 return;
             }
             start = true;
@@ -85,7 +89,7 @@
         if (server_state->node_type_tracker == NULL) {
             server_state->node_type_tracker = node_type_tracker_create(server_state);
             if (server_state->node_type_tracker == NULL) {
-                FAULT("can't start node type tracker.");
+                FAULT("can't create node type tracker.");
                 return;
             }
             start = true;
@@ -93,7 +97,7 @@
         if (server_state->service_publisher == NULL) {
             server_state->service_publisher = service_publisher_create(server_state);
             if (server_state->service_publisher == NULL) {
-                FAULT("can't start service publisher.");
+                FAULT("can't create service publisher.");
                 return;
             }
             start = true;
@@ -101,9 +105,10 @@
         if (server_state->dnssd_client == NULL) {
             server_state->dnssd_client = dnssd_client_create(server_state);
             if (server_state->dnssd_client == NULL) {
-                FAULT("can't start dnssd client");
+                FAULT("can't create dnssd client");
                 return;
             }
+            start = true;
         }
         if (start) {
             thread_tracker_start(server_state->thread_tracker);
@@ -113,10 +118,18 @@
         }
     }
 }
+
 // Start browsing for SRP service, and, if it makes sense, advertise as an SRP server.
 void
 thread_device_startup(srp_server_t *NONNULL server_state)
 {
+    // Just in case we get called without a shutdown having happened, before starting up again, do the
+    // shutdown. This will be a no-op if it has already been done. Don't flush missing services, since we might
+    // get a request for a missing service before we get the thread startup command.
+    thread_device_shutdown(server_state);
+
+    INFO("starting up");
+
     // Before we can actually do anything, we need our RLOC16.
     int status = cti_get_rloc16(server_state, &server_state->thread_rloc16_context, server_state,
                                 thread_device_rloc16_callback, NULL);
@@ -128,7 +141,9 @@
 void
 thread_device_stop(srp_server_t *NONNULL server_state)
 {
+    INFO("stopping");
     if (server_state->service_tracker != NULL) {
+        service_tracker_cancel_probes(server_state->service_tracker);
         service_tracker_cancel(server_state->service_tracker);
         service_tracker_release(server_state->service_tracker);
         server_state->service_tracker = NULL;
@@ -158,10 +173,15 @@
 void
 thread_device_shutdown(srp_server_t *NONNULL server_state)
 {
+    INFO("shutting down");
     if (server_state->thread_rloc16_context != NULL) {
         cti_events_discontinue(server_state->thread_rloc16_context);
         server_state->thread_rloc16_context = NULL;
     }
+    if (server_state->wed_tracker != NULL) {
+        cti_events_discontinue(server_state->wed_tracker);
+        server_state->wed_tracker = NULL;
+    }
     thread_device_stop(server_state);
 }
 
diff --git a/ServiceRegistration/thread-service.c b/ServiceRegistration/thread-service.c
index 0faee12..2ecd2b5 100644
--- a/ServiceRegistration/thread-service.c
+++ b/ServiceRegistration/thread-service.c
@@ -144,12 +144,15 @@
 thread_service_note(const char *owner_id, thread_service_t *tservice, const char *event_description)
 {
     switch(tservice->service_type) {
+    default:
+        INFO("invalid service type %d on service %p", tservice->service_type, tservice);
+        break;
     case unicast_service:
         {
             struct thread_unicast_service *service = &tservice->u.unicast;
             uint16_t port;
 
-            port = (service->port[0] << 8) | service->port[1];
+            port = (uint16_t)(service->port[0] << 8) | service->port[1];
             SEGMENTED_IPv6_ADDR_GEN_SRP(&service->address, service_add_buf);
             INFO(PUB_S_SRP " SRP service " PRI_SEGMENTED_IPv6_ADDR_SRP "%%%d, rloc16 %x " PUB_S_SRP, owner_id,
                  SEGMENTED_IPv6_ADDR_PARAM_SRP(&service->address, service_add_buf),
@@ -191,6 +194,8 @@
         return false;
     }
     switch(a->service_type) {
+    default:
+        return false;
     case unicast_service:
         {
             return (!in6addr_compare(&a->u.unicast.address, &b->u.unicast.address) &&
diff --git a/ServiceRegistration/thread-service.h b/ServiceRegistration/thread-service.h
index f793701..fd8c2dc 100644
--- a/ServiceRegistration/thread-service.h
+++ b/ServiceRegistration/thread-service.h
@@ -22,6 +22,7 @@
 #define __THREAD_SERVICE_H__ 1
 
 typedef struct thread_service thread_service_t;
+typedef struct probe_state probe_state_t;
 
 typedef enum {
     add_complete,
@@ -40,8 +41,9 @@
 };
 
 struct thread_unicast_service {
-    struct in6_addr address; // IPv6 address on which service is offered
-    uint8_t port[2];         // Port (network byte order)
+    struct in6_addr address;   // IPv6 address on which service is offered
+    uint8_t port[2];           // Port (network byte order)
+    bool anycast_also_present; // True if the RLOC16 advertising this service is also advertising anycast
 };
 
 struct thread_anycast_service {
@@ -49,7 +51,7 @@
     uint8_t sequence_number;
 };
 
-typedef enum { pref_id, unicast_service, anycast_service } thread_service_type_t;
+typedef enum { any_service, pref_id, unicast_service, anycast_service } thread_service_type_t;
 
 struct thread_service {
     int ref_count;
@@ -57,7 +59,7 @@
     uint16_t rloc16;
     uint8_t service_id;
     thread_service_type_t service_type;
-    bool user, ncp, stable, ignore, checking, remove;
+    bool user, ncp, stable, ignore, checking;
     bool previous_user, previous_ncp, previous_stable;
 	thread_service_publication_state_t publication_state;
     time_t last_probe_time;
@@ -72,6 +74,8 @@
 };
 
 RELEASE_RETAIN_DECLS(thread_service);
+#define thread_service_retain(watcher) thread_service_retain_(watcher, __FILE__, __LINE__)
+#define thread_service_release(watcher) thread_service_release_(watcher, __FILE__, __LINE__)
 void thread_service_list_release(thread_service_t *NONNULL *NULLABLE list_pointer);
 #define thread_service_unicast_create(rloc16, address, port, service_id) \
 	thread_service_unicast_create_(rloc16, address, port, service_id, __FILE__, __LINE__)
diff --git a/ServiceRegistration/thread-tracker.c b/ServiceRegistration/thread-tracker.c
index 96e2612..d0c1ad6 100644
--- a/ServiceRegistration/thread-tracker.c
+++ b/ServiceRegistration/thread-tracker.c
@@ -64,7 +64,7 @@
     srp_server_t *server_state;
     cti_connection_t NULLABLE thread_context;
 	thread_tracker_callback_t *callbacks;
-	time_t last_thread_network_state_change;
+	uint64_t last_thread_network_state_change;
 	thread_network_state_t current_state, previous_state;
 	bool associated, previous_associated;
 };
@@ -168,8 +168,8 @@
         break;
     }
 
-	if ((state == kCTI_NCPState_Associated)     || (state == kCTI_NCPState_Isolated) ||
-		(state == kCTI_NCPState_NetWake_Asleep) || (state == kCTI_NCPState_NetWake_Waking))
+	if ((cti_state == kCTI_NCPState_Associated)     || (cti_state == kCTI_NCPState_Isolated) ||
+		(cti_state == kCTI_NCPState_NetWake_Asleep) || (cti_state == kCTI_NCPState_NetWake_Waking))
 	{
 		associated = true;
 	}
diff --git a/ServiceRegistration/thread-tracker.h b/ServiceRegistration/thread-tracker.h
index b77fe5f..1170e66 100644
--- a/ServiceRegistration/thread-tracker.h
+++ b/ServiceRegistration/thread-tracker.h
@@ -39,6 +39,8 @@
 } thread_network_state_t;
 
 RELEASE_RETAIN_DECLS(thread_tracker);
+#define thread_tracker_retain(watcher) thread_tracker_retain_(watcher, __FILE__, __LINE__)
+#define thread_tracker_release(watcher) thread_tracker_release_(watcher, __FILE__, __LINE__)
 const char *NONNULL thread_tracker_network_state_to_string(thread_network_state_t state);
 void thread_tracker_cancel(thread_tracker_t *NONNULL publisher);
 thread_tracker_t *NULLABLE thread_tracker_create(srp_server_t *NONNULL route_state);
diff --git a/ServiceRegistration/verify-macos.c b/ServiceRegistration/verify-macos.c
index b7c0534..b6763d9 100644
--- a/ServiceRegistration/verify-macos.c
+++ b/ServiceRegistration/verify-macos.c
@@ -35,15 +35,18 @@
 
 //======================================================================================================================
 
+#if !TARGET_OS_OSX
 static SecKeyRef
 create_public_sec_key(const dns_rr_t *const key_record);
 
 static CFDataRef
 create_data_to_verify(dns_wire_t *const message, const dns_rr_t *const signature);
+#endif // !TARGET_OS_OSX
 
 bool
 srp_sig0_verify(dns_wire_t *message, dns_rr_t *key, dns_rr_t *signature)
 {
+#if !TARGET_OS_OSX
     bool valid = false;
     CFErrorRef cf_error = NULL;
     SecKeyRef public_key = NULL;
@@ -85,7 +88,7 @@
     SecKeyAlgorithm verify_algorithm;
     switch (key->data.key.algorithm) {
         case dnssec_keytype_ecdsa:
-            verify_algorithm = kSecKeyAlgorithmECDSASignatureRFC4754;
+            verify_algorithm = kSecKeyAlgorithmECDSASignatureDigestRFC4754;
             break;
 
         default:
@@ -96,8 +99,10 @@
     // Validate the signature.
     valid = SecKeyVerifySignature(public_key, verify_algorithm, data_to_verify_cfdata, sig_to_match_cfdata, &cf_error);
     if (!valid) {
+        char errbuf[200];
         CFStringRef error_cfstring = CFErrorCopyDescription(cf_error);
-        ERROR("SecKeyVerifySignature failed to validate - Error Description: %@", error_cfstring);
+        CFStringGetCString(error_cfstring, errbuf, sizeof(errbuf), kCFStringEncodingUTF8);
+        ERROR("SecKeyVerifySignature failed to validate - Error Description: %s", errbuf);
         CFRelease(error_cfstring);
         CFRelease(cf_error);
         cf_error = NULL;
@@ -115,8 +120,16 @@
     }
 
     return valid;
+#else
+    (void)message;
+    (void)key;
+    (void)signature;
+
+    return true;
+#endif // !TARGET_OS_OSX
 }
 
+#if !TARGET_OS_OSX
 static SecKeyRef
 create_public_sec_key(const dns_rr_t *const key_record)
 {
@@ -232,6 +245,7 @@
     }
     return data_to_verify_cfdata;
 }
+#endif // !TARGET_OS_OSX
 
 //======================================================================================================================
 
diff --git a/ServiceRegistration/wireutils.c b/ServiceRegistration/wireutils.c
index db72945..c435255 100644
--- a/ServiceRegistration/wireutils.c
+++ b/ServiceRegistration/wireutils.c
@@ -200,10 +200,11 @@
                 if (ix + 5 >= bufmax) {
                     break;
                 }
+                uint8_t unsigned_char = lp->data[i];
                 buf[ix++] = '\\';
-                buf[ix++] = '0' + (lp->data[i] / 100);
-                buf[ix++] = '0' + (lp->data[i] /  10) % 10;
-                buf[ix++] = '0' + lp->data[i]         % 10;
+                buf[ix++] = '0' + (unsigned_char / 100);
+                buf[ix++] = '0' + (unsigned_char /  10) % 10;
+                buf[ix++] = '0' + unsigned_char         % 10;
             }
         }
         if (i != lp->len) {
@@ -664,6 +665,16 @@
             dns_rdata_raw_data_to_wire(towire, rr->data.unparsed.data, rr->data.unparsed.len);
             break;
 
+        case dns_rrtype_soa:
+            dns_concatenate_name_to_wire(towire, rr->data.soa.mname, NULL, NULL);
+            dns_concatenate_name_to_wire(towire, rr->data.soa.rname, NULL, NULL);
+            dns_u32_to_wire(towire, rr->data.soa.serial);
+            dns_u32_to_wire(towire, rr->data.soa.refresh);
+            dns_u32_to_wire(towire, rr->data.soa.retry);
+            dns_u32_to_wire(towire, rr->data.soa.expire);
+            dns_u32_to_wire(towire, rr->data.soa.minimum);
+            break;
+
             // All have a single name as the data
         case dns_rrtype_ptr:
         case dns_rrtype_ns:
diff --git a/mDNSCore/DNSCommon.c b/mDNSCore/DNSCommon.c
index 8a5374d..8ee9c71 100644
--- a/mDNSCore/DNSCommon.c
+++ b/mDNSCore/DNSCommon.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4; c-file-style: "bsd"; c-basic-offset: 4; fill-column: 108; indent-tabs-mode: nil; -*-
  *
- * Copyright (c) 2002-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -25,6 +25,10 @@
 #include "discover_resolver.h"
 #endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
+#include "dns_push_discovery.h"
+#endif
+
 #if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
 #include "dnssec_obj_rr_ds.h"   // For dnssec_obj_rr_ds_t.
 #include "dnssec_mdns_core.h"   // For DNSSEC-related operation on mDNSCore structures.
@@ -193,6 +197,46 @@
     return mDNStrue;
 }
 
+NetworkInterfaceInfo *FirstInterfaceForID(mDNS *const m, const mDNSInterfaceID InterfaceID)
+{
+    NetworkInterfaceInfo *intf = m->HostInterfaces;
+    while (intf && intf->InterfaceID != InterfaceID) intf = intf->next;
+    return(intf);
+}
+
+NetworkInterfaceInfo *FirstIPv4LLInterfaceForID(mDNS *const m, const mDNSInterfaceID InterfaceID)
+{
+    NetworkInterfaceInfo *intf;
+
+    if (!InterfaceID)
+        return mDNSNULL;
+
+    // Note: We don't check for InterfaceActive, as the active interface could be IPv6 and
+    // we still want to find the first IPv4 Link-Local interface
+    for (intf = m->HostInterfaces; intf; intf = intf->next)
+    {
+        if (intf->InterfaceID == InterfaceID &&
+            intf->ip.type == mDNSAddrType_IPv4 && mDNSv4AddressIsLinkLocal(&intf->ip.ip.v4))
+        {
+            debugf("FirstIPv4LLInterfaceForID: found LL interface with address %.4a", &intf->ip.ip.v4);
+            return intf;
+        }
+    }
+    return (mDNSNULL);
+}
+
+mDNSexport char *InterfaceNameForID(mDNS *const m, const mDNSInterfaceID InterfaceID)
+{
+    NetworkInterfaceInfo *intf = FirstInterfaceForID(m, InterfaceID);
+    return(intf ? intf->ifname : mDNSNULL);
+}
+
+mDNSexport const char *InterfaceNameForIDOrEmptyString(const mDNSInterfaceID InterfaceID)
+{
+    const char *const ifName = InterfaceNameForID(&mDNSStorage, InterfaceID);
+    return (ifName ? ifName : "");
+}
+
 mDNSexport NetworkInterfaceInfo *GetFirstActiveInterface(NetworkInterfaceInfo *intf)
 {
     while (intf && !intf->InterfaceActive) intf = intf->next;
@@ -516,6 +560,12 @@
                 length += mDNS_snprintf(buffer+length, RemSpc, " Platform %d",    opt->u.tracer.platf);
                 length += mDNS_snprintf(buffer+length, RemSpc, " mDNSVers %d",    opt->u.tracer.mDNSv);
                 break;
+            case kDNSOpt_TSR:
+                length += mDNS_snprintf(buffer+length, RemSpc, " TSR");
+                length += mDNS_snprintf(buffer+length, RemSpc, " Tm %d", opt->u.tsr.timeStamp);
+                length += mDNS_snprintf(buffer+length, RemSpc, " Hk %x", opt->u.tsr.hostkeyHash);
+                length += mDNS_snprintf(buffer+length, RemSpc, " Ix %u", opt->u.tsr.recIndex);
+                break;
             default:
                 length += mDNS_snprintf(buffer+length, RemSpc, " Unknown %d",  opt->opt);
                 break;
@@ -630,6 +680,23 @@
     return(buffer);
 }
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
+
+mDNSexport const mDNSu8 *GetPrintableRDataBytes(mDNSu8 *const outBuffer, const mDNSu32 bufferLen,
+	const mDNSu16 recordType, const mDNSu8 * const rdata, const mDNSu32 rdataLen)
+{
+	const mDNSu32 totalLen = rdataLen + 2;
+	mdns_require_return_value(bufferLen >= totalLen, mDNSNULL);
+
+	outBuffer[0] = (mDNSu8)((recordType >> 8) & 0xFF);
+	outBuffer[1] = (mDNSu8)((recordType     ) & 0xFF);
+	mDNSPlatformMemCopy(&outBuffer[2], rdata, (mDNSu32)rdataLen);
+
+	return outBuffer;
+}
+
+#endif
+
 // See comments in mDNSEmbeddedAPI.h
 #if _PLATFORM_HAS_STRONG_PRNG_
 #define mDNSRandomNumber mDNSPlatformRandomNumber
@@ -698,6 +765,8 @@
             }
         }
             break;
+        MDNS_COVERED_SWITCH_DEFAULT:
+            break;
     }
 
     return hash;
@@ -711,8 +780,27 @@
                 len);
         case mDNSNonCryptoHash_SDBM:
             return mDNS_NonCryptoHashUpdateBytes(mDNSNonCryptoHash_SDBM, 0, bytes, len);
+        MDNS_COVERED_SWITCH_DEFAULT:
+            return 0;
     }
-    return 0;
+}
+
+mDNSexport mDNSu32 mDNS_DomainNameFNV1aHash(const domainname *const name)
+{
+    mDNSu32 hash = MDNSRESPONDER_FNV_32_BIT_OFFSET_BASIS;
+    const mDNSu32 len = DomainNameLength(name);
+    const mDNSu8 *const data = name->c;
+    for (mDNSu32 i = 0; i < len; ++i)
+    {
+        hash ^= mDNSASCIITolower(data[i]);
+        hash *= MDNSRESPONDER_FNV_32_BIT_PRIME;
+    }
+    return hash;
+}
+
+mDNSexport mDNSs32 mDNSGetTimeOfDay(struct timeval *const tv, struct timezone *const tz)
+{
+    return gettimeofday(tv, tz);
 }
 
 mDNSexport mDNSBool mDNSSameAddress(const mDNSAddr *ip1, const mDNSAddr *ip2)
@@ -724,6 +812,8 @@
         case mDNSAddrType_None: return(mDNStrue);      // Empty addresses have no data and are therefore always equal
         case mDNSAddrType_IPv4: return (mDNSBool)(mDNSSameIPv4Address(ip1->ip.v4, ip2->ip.v4));
         case mDNSAddrType_IPv6: return (mDNSBool)(mDNSSameIPv6Address(ip1->ip.v6, ip2->ip.v6));
+        default:
+            break;
         }
     }
     return(mDNSfalse);
@@ -1996,8 +2086,8 @@
     (void) flags;
 #endif
 
-    // TSR should not answer any questions.
-    if (rr->rrtype == kDNSType_TSR)
+    // OPT should not answer any questions.
+    if (rr->rrtype == kDNSType_OPT)
     {
         return mDNSfalse;
     }
@@ -2039,6 +2129,24 @@
     return mDNSfalse;
 }
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+mDNSlocal mDNSBool RRMatchesQuestionService(const ResourceRecord *const rr, const DNSQuestion *const q)
+{
+    return mdns_cache_metadata_get_dns_service(rr->metadata) == q->dnsservice;
+}
+#endif
+
+mDNSlocal mDNSBool RRIsResolvedBymDNS(const ResourceRecord *const rr)
+{
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    if (mdns_cache_metadata_get_dns_service(rr->metadata))
+    {
+        return mDNSfalse;
+    }
+#endif
+    return (rr->InterfaceID != 0);
+}
+
 // ResourceRecordAnswersQuestion returns mDNStrue if the given resource record is a valid answer to the given question.
 // SameNameRecordAnswersQuestion is the same, except it skips the expensive SameDomainName() call.
 // SameDomainName() is generally cheap when the names don't match, but expensive when they do match,
@@ -2062,21 +2170,42 @@
         q->InterfaceID && q->InterfaceID != mDNSInterface_LocalOnly &&
         rr->InterfaceID != q->InterfaceID) return(mDNSfalse);
 
-    // Resource record received via unicast, the resolver group ID should match ?
-    if (!isAuthRecord && !rr->InterfaceID)
+#if MDNSRESPONDER_SUPPORTS(APPLE, TERMINUS_ASSISTED_UNICAST_DISCOVERY)
+    if (DNSQuestionUsesMDNSAlternativeService(q))
     {
-        if (mDNSOpaque16IsZero(q->TargetQID)) return(mDNSfalse);
-#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
-        if (mdns_cache_metadata_get_dns_service(rr->metadata) != q->dnsservice) return(mDNSfalse);
-#else
-        const mDNSu32 idr = rr->rDNSServer ? rr->rDNSServer->resGroupID : 0;
-        const mDNSu32 idq = q->qDNSServer ? q->qDNSServer->resGroupID : 0;
-        if (idr != idq) return(mDNSfalse);
-#endif
+        if (!RRMatchesQuestionService(rr, q))
+        {
+            return mDNSfalse;
+        }
     }
+    else
+#endif
+    {
+        const mDNSBool resolvedBymDNS = RRIsResolvedBymDNS(rr);
+        mDNSBool ismDNSQuestion = mDNSOpaque16IsZero(q->TargetQID);
 
-    // If ResourceRecord received via multicast, but question was unicast, then shouldn't use record to answer this question
-    if (rr->InterfaceID && !mDNSOpaque16IsZero(q->TargetQID)) return(mDNSfalse);
+        // If the record is resolved via the non-mDNS channel, the server or service used should match.
+        if (!isAuthRecord && !resolvedBymDNS)
+        {
+            if (ismDNSQuestion)
+            {
+                return mDNSfalse;
+            }
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+            if (!RRMatchesQuestionService(rr, q)) return(mDNSfalse);
+#else
+            const mDNSu32 idr = rr->rDNSServer ? rr->rDNSServer->resGroupID : 0;
+            const mDNSu32 idq = q->qDNSServer ? q->qDNSServer->resGroupID : 0;
+            if (idr != idq) return(mDNSfalse);
+#endif
+        }
+
+        // mDNS records can only be used to answer mDNS questions.
+        if (resolvedBymDNS && !ismDNSQuestion)
+        {
+            return mDNSfalse;
+        }
+    }
 
     // CNAME answers question of any type and a negative cache record should not prevent us from querying other
     // valid types at the same name.
@@ -2242,25 +2371,42 @@
         q->InterfaceID && q->InterfaceID != mDNSInterface_LocalOnly &&
         rr->InterfaceID != q->InterfaceID) return(mDNSfalse);
 
-    // Resource record received via unicast, the resolver group ID should match ?
-    // Note that Auth Records are normally setup with NULL InterfaceID and
-    // both the DNSServers are assumed to be NULL in that case
-    if (!rr->InterfaceID)
+#if MDNSRESPONDER_SUPPORTS(APPLE, TERMINUS_ASSISTED_UNICAST_DISCOVERY)
+    if (DNSQuestionUsesMDNSAlternativeService(q))
     {
+        if (!RRMatchesQuestionService(rr, q))
+        {
+            return mDNSfalse;
+        }
+    }
+    else
+#endif
+    {
+        const mDNSBool resolvedByMDNS = RRIsResolvedBymDNS(rr);
+        // Resource record received via non-mDNS channel, the server or service should match.
+        // Note that Auth Records are normally setup with NULL InterfaceID and
+        // both the DNSServers are assumed to be NULL in that case
+        if (!resolvedByMDNS)
+        {
 #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
-        if (mdns_cache_metadata_get_dns_service(rr->metadata) != q->dnsservice) return(mDNSfalse);
+            if (!RRMatchesQuestionService(rr, q)) return(mDNSfalse);
 #else
-        const mDNSu32 idr = rr->rDNSServer ? rr->rDNSServer->resGroupID : 0;
-        const mDNSu32 idq = q->qDNSServer ? q->qDNSServer->resGroupID : 0;
-        if (idr != idq) return(mDNSfalse);
+            const mDNSu32 idr = rr->rDNSServer ? rr->rDNSServer->resGroupID : 0;
+            const mDNSu32 idq = q->qDNSServer ? q->qDNSServer->resGroupID : 0;
+            if (idr != idq) return(mDNSfalse);
 #endif
 #if MDNSRESPONDER_SUPPORTS(APPLE, RANDOM_AWDL_HOSTNAME)
-        if (!mDNSPlatformValidRecordForInterface(ar, q->InterfaceID)) return(mDNSfalse);
+            if (!mDNSPlatformValidRecordForInterface(ar, q->InterfaceID)) return(mDNSfalse);
 #endif
-    }
+        }
 
-    // If ResourceRecord received via multicast, but question was unicast, then shouldn't use record to answer this question
-    if (rr->InterfaceID && !mDNSOpaque16IsZero(q->TargetQID)) return(mDNSfalse);
+        // mDNS records can only be used to answer mDNS questions.
+        const mDNSBool isMDNSQuestion = mDNSOpaque16IsZero(q->TargetQID);
+        if (resolvedByMDNS && !isMDNSQuestion)
+        {
+            return mDNSfalse;
+        }
+    }
 
     if (rr->rrclass != q->qclass && q->qclass != kDNSQClass_ANY) return(mDNSfalse);
 
@@ -2277,12 +2423,17 @@
     if (q->Suppressed)
         return mDNSfalse;
 
-    // For resource records created using multicast, the InterfaceIDs have to match
+    // For resource records created using multicast or DNS push, the InterfaceIDs have to match.
     if (rr->InterfaceID &&
         q->InterfaceID && rr->InterfaceID != q->InterfaceID) return(mDNSfalse);
 
-    // If ResourceRecord received via multicast, but question was unicast, then shouldn't use record to answer this question.
-    if (rr->InterfaceID && !mDNSOpaque16IsZero(q->TargetQID)) return(mDNSfalse);
+    // If record is resolved by mDNS, but question is non-mDNS, then should not use it to answer this question.
+    const mDNSBool resolvedByMDNS = RRIsResolvedBymDNS(rr);
+    const mDNSBool isMDNSQuestion = mDNSOpaque16IsZero(q->TargetQID);
+    if (resolvedByMDNS && !isMDNSQuestion)
+    {
+        return mDNSfalse;
+    }
 
     // RR type CNAME matches any query type. QTYPE ANY matches any RR type. QCLASS ANY matches any RR class.
     RRTypeAnswersQuestionTypeFlags flags = kRRTypeAnswersQuestionTypeFlagsNone;
@@ -2427,6 +2578,46 @@
     }
 }
 
+mDNSexport const mDNSu8 * ResourceRecordGetRDataBytesPointer(const ResourceRecord *const rr,
+    mDNSu8 * const bytesBuffer, const mDNSu16 bufferSize, mDNSu16 *const outRDataLen, mStatus *const outError)
+{
+    mStatus err;
+    const mDNSu8 *rdataBytes = mDNSNULL;
+    mDNSu16 rdataLen = 0;
+    switch (rr->rrtype)
+    {
+        case kDNSType_SOA:
+        case kDNSType_MX:
+        case kDNSType_AFSDB:
+        case kDNSType_RT:
+        case kDNSType_RP:
+        case kDNSType_SRV:
+        case kDNSType_PX:
+        case kDNSType_KX:
+        case kDNSType_OPT:
+        case kDNSType_NSEC:
+        case kDNSType_TSR:
+        {
+            const mDNSu8 *const rdataBytesEnd = putRData(mDNSNULL, bytesBuffer, bytesBuffer + bufferSize, rr);
+            mdns_require_action_quiet(rdataBytesEnd && (rdataBytesEnd > bytesBuffer), exit, err = mStatus_BadParamErr);
+
+            rdataBytes = bytesBuffer;
+            rdataLen = (rdataBytesEnd - bytesBuffer);
+            break;
+        }
+        default:
+            rdataBytes = rr->rdata->u.data;
+            rdataLen = rr->rdlength;
+            break;
+    }
+    err = mStatus_NoError;
+
+exit:
+    mdns_assign(outRDataLen, rdataLen);
+    mdns_assign(outError, err);
+    return rdataBytes;
+}
+
 // ***************************************************************************
 // MARK: - DNS Message Creation Functions
 
@@ -2655,12 +2846,12 @@
         int len = 0;
         const rdataOPT *opt;
         const rdataOPT *const end = (const rdataOPT *)&rr->rdata->u.data[rr->rdlength];
-        for (opt = &rr->rdata->u.opt[0]; opt < end; opt++) 
+        for (opt = &rr->rdata->u.opt[0]; opt < end; opt++)
             len += DNSOpt_Data_Space(opt);
-        if (ptr + len > limit) 
-        { 
-            LogMsg("ERROR: putOptRData - out of space"); 
-            return mDNSNULL; 
+        if (ptr + len > limit)
+        {
+            LogMsg("ERROR: putOptRData - out of space");
+            return mDNSNULL;
         }
         for (opt = &rr->rdata->u.opt[0]; opt < end; opt++)
         {
@@ -2700,6 +2891,16 @@
                 *ptr++ = opt->u.tracer.platf;
                 ptr    = putVal32(ptr, opt->u.tracer.mDNSv);
                 break;
+            case kDNSOpt_TSR:
+                {
+                    mDNSs32 tsr_relative = mDNSPlatformContinuousTimeSeconds() - opt->u.tsr.timeStamp;
+                    ptr = putVal32(ptr, tsr_relative);
+                    ptr = putVal32(ptr, opt->u.tsr.hostkeyHash);
+                    ptr = putVal16(ptr, opt->u.tsr.recIndex);
+                }
+                break;
+            default:
+                break;
             }
         }
         return ptr;
@@ -3070,6 +3271,8 @@
         case 0xC0:  if (ptr + 1 > end)                          // Skip the two-byte name compression pointer.
             { debugf("skipDomainName: Malformed compression pointer (overruns packet end)"); return(mDNSNULL); }
             return(ptr + 1);
+        default:
+            break;
         }
     }
 }
@@ -3120,6 +3323,9 @@
             if (*ptr & 0xC0)
             { debugf("getDomainName: Compression pointer must point to real label"); return(mDNSNULL); }
             break;
+
+        default:
+            break;
         }
     }
 
@@ -3630,6 +3836,17 @@
                     opt++;
                 }
                 break;
+            case kDNSOpt_TSR:
+                if (opt->optlen == DNSOpt_TSRData_Space - 4)
+                {
+                    opt->u.tsr.timeStamp    = (mDNSs32) ((mDNSu32)ptr[0] << 24 | (mDNSu32)ptr[1] << 16 | (mDNSu32)ptr[2] << 8 | ptr[3]);
+                    opt->u.tsr.hostkeyHash  = (mDNSu32) ((mDNSu32)ptr[4] << 24 | (mDNSu32)ptr[5] << 16 | (mDNSu32)ptr[6] << 8 | ptr[7]);
+                    opt->u.tsr.recIndex     = (mDNSu16) ((mDNSu16)ptr[8] << 8 | ptr[9]);
+                    opt++;
+                }
+                break;
+            default:
+                break;
             }
             ptr += currentopt->optlen;
         }
@@ -4218,7 +4435,7 @@
     static mDNSu32 previousMsg2ndHashes[NUM_OF_SAVED_HASH_COUNT] = {0};
     static mDNSu32 nextMsgHashSlot = 0;
     static mDNSu32 nextMsgHashUninitializedSlot = 0;
-    check_compile_time_code(mdns_countof(previousMsgHashes) == mdns_countof(previousMsg2ndHashes));
+    mdns_compile_time_check_local(mdns_countof(previousMsgHashes) == mdns_countof(previousMsg2ndHashes));
 
     mDNSBool msgHashSame = mDNSfalse;
     count = Min(mdns_countof(previousMsgHashes), nextMsgHashUninitializedSlot);
@@ -4247,7 +4464,7 @@
     static mDNSu32 previousComplete2ndHashes[NUM_OF_SAVED_HASH_COUNT] = {0};
     static mDNSu32 nextCompleteHashSlot = 0;
     static mDNSu32 nextCompleteHashUninitializedSlot = 0;
-    check_compile_time_code(mdns_countof(previousCompleteHashes) == mdns_countof(previousComplete2ndHashes));
+    mdns_compile_time_check_local(mdns_countof(previousCompleteHashes) == mdns_countof(previousComplete2ndHashes));
 
     mDNSBool completeHashSame = mDNSfalse;
     count = Min(mdns_countof(previousCompleteHashes), nextCompleteHashUninitializedSlot);
@@ -5058,6 +5275,40 @@
 }
 #endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, TERMINUS_ASSISTED_UNICAST_DISCOVERY)
+
+mDNSlocal mDNSBool DNSQuestionUsesAWDL(const DNSQuestion *const q)
+{
+    if (q->InterfaceID == mDNSInterface_Any)
+    {
+        return ((q->flags & kDNSServiceFlagsIncludeAWDL) != 0);
+    }
+    else
+    {
+        return mDNSPlatformInterfaceIsAWDL(q->InterfaceID);
+    }
+}
+
+mDNSBool DNSQuestionIsEligibleForMDNSAlternativeService(const DNSQuestion *const q)
+{
+    // 0. The system is not in a demo mode where mDNS traffic is ensured to be lossless in a wired connection.
+    // 1. The question must be an mDNS question.
+    // 2. The question cannot enable resolution over AWDL.
+    //    (because the resolution over mDNS alternative service is mutual exclusive with the resolution over AWDL)
+    return (!is_airplay_demo_mode_enabled() && mDNSOpaque16IsZero(q->TargetQID) && !DNSQuestionUsesAWDL(q));
+}
+
+mDNSBool DNSQuestionRequestsMDNSAlternativeService(const DNSQuestion *const q)
+{
+    return (!mDNSOpaque16IsZero(q->TargetQID) && !Question_uDNS(q));
+}
+
+mDNSBool DNSQuestionUsesMDNSAlternativeService(const DNSQuestion *const q)
+{
+    return q->dnsservice && mdns_dns_service_is_mdns_alternative(q->dnsservice);
+}
+#endif
+
 // ***************************************************************************
 // MARK: - RR List Management & Task Management
 
@@ -5763,6 +6014,8 @@
                         s = mDNS_VACB;                  // Reset s back to the start of the buffer
                         break;
                     }
+                    default:
+                        break;
                     }
                 // Make sure we don't truncate in the middle of a UTF-8 character (see similar comment below)
                 if (F.havePrecision && i > F.precision)
diff --git a/mDNSCore/DNSCommon.h b/mDNSCore/DNSCommon.h
index 5f74f01..6758ab7 100644
--- a/mDNSCore/DNSCommon.h
+++ b/mDNSCore/DNSCommon.h
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2002-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -20,6 +20,13 @@
 
 #include "mDNSEmbeddedAPI.h"
 
+// For gettimeofday
+#if !defined(_WIN32)
+#include <sys/time.h>
+#else
+#include "PosixCompat.h"
+#endif
+
 #if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
 #include "dnssec_mdns_core.h"
 #endif
@@ -169,6 +176,28 @@
  */
 extern mDNSu32 mDNS_NonCryptoHash(mDNSNonCryptoHash algorithm, const mDNSu8 *bytes, mDNSu32 len);
 
+/*!
+ *    @brief
+ *      Computes the 32-bit FNV-1a (non-cryptographic) hash value for an domain name.
+ *
+ *    @param name
+ *      The domain name.
+ *
+ *    @result
+ *      The hash value.
+ *
+ *    @discussion
+ *      Since domain name is case-insensitive, to make sure that hash values still match when the case changes,
+ *      the hash value is calculated with the normalized domain name by treating uppercase ASCII letters to their
+ *      lowercase counterparts.
+ *
+ *      For more information about FNV Non-Cryptographic Hash ,
+ *      see <https://datatracker.ietf.org/doc/html/draft-eastlake-fnv-21>.
+ */
+extern mDNSu32 mDNS_DomainNameFNV1aHash(const domainname *name);
+
+extern mDNSs32 mDNSGetTimeOfDay(struct timeval *tv, struct timezone *tz);
+
 // ***************************************************************************
 // MARK: - Domain Name Utility Functions
 
@@ -182,6 +211,29 @@
 
 /*!
  *  @brief
+ *      Convert ASCII uppercase character to its lowercase counterparts.
+ *
+ *  @param c
+ *      The ASCII character.
+ *
+ *  @result
+ *      The lowercase value of the character, if the original one is uppercase, otherwise, the original value.
+ */
+static inline int
+mDNSASCIITolower(const int c)
+{
+    if (mDNSIsUpperCase(c))
+    {
+        return (c + ('a' - 'A'));
+    }
+    else
+    {
+        return c;
+    }
+}
+
+/*!
+ *  @brief
  *      Check if the string consists of all valid UTF-8 characters.
  *
  *  @param str
@@ -314,6 +366,35 @@
 extern mDNSBool ValidateRData(const mDNSu16 rrtype, const mDNSu16 rdlength, const RData *const rd);
 extern mStatus DNSNameToLowerCase(domainname *d, domainname *result);
 
+/*!
+ *  @brief
+ *      Gets a pointer to a resource record's record data in wire format.
+ *
+ *  @param rr
+ *      The resource record object.
+ *
+ *  @param bytesBuffer
+ *      The buffer to be used as a temporary space to hold a resource record's record data in wire format if no
+ *      existing wire-format rdata is available.
+ *
+ *  @param bufferSize
+ *      The size of the buffer.
+ *
+ *  @param outRDataLen
+ *      If non-NULL, the address of a variable to set to the length of the resource record's record data in
+ *      wire format.
+ *
+ *  @param outError
+ *      If non-NULL, the address of a variable to set to either a non-zero error code if this function fails, or
+ *      `mStatus_NoError` if this function succeeds.
+ *
+ *  @result
+ *      The pointer to the resource record's record data in wire format if no error occurs. Otherwise mDNSNULL
+ *      and `outError` is set to a non-zero error code.
+ */
+extern const mDNSu8 * ResourceRecordGetRDataBytesPointer(const ResourceRecord *rr, mDNSu8 *bytesBuffer,
+    mDNSu16 bufferSize, mDNSu16 *outRDataLen, mStatus *outError);
+
 #define GetRRDomainNameTarget(RR) (                                                                          \
         ((RR)->rrtype == kDNSType_NS || (RR)->rrtype == kDNSType_CNAME || (RR)->rrtype == kDNSType_PTR || (RR)->rrtype == kDNSType_DNAME) ? &(RR)->rdata->u.name        : \
         ((RR)->rrtype == kDNSType_MX || (RR)->rrtype == kDNSType_AFSDB || (RR)->rrtype == kDNSType_RT  || (RR)->rrtype == kDNSType_KX   ) ? &(RR)->rdata->u.mx.exchange : \
@@ -345,10 +426,24 @@
 
 #define PutResourceRecord(MSG, P, C, RR) PutResourceRecordTTL((MSG), (P), (C), (RR), (RR)->rroriginalttl)
 
+// Calculate TSR only OPT space
+// Assume local variable 'tsrOptsCount'
+#define TSR_OPT_SPACE           (tsrOptsCount * DNSOpt_TSRData_Space)
+#define TSR_OPT_HEADER_SPACE    (tsrOptsCount ? DNSOpt_Header_Space : 0)
+#define TSR_OPT_TOTAL_SPACE     (TSR_OPT_SPACE + TSR_OPT_HEADER_SPACE)
+
+#define PutResourceRecordTSR(msg, ptr, count, rr) \
+    PutResourceRecordTTLWithLimit((msg), (ptr), (count), (rr), (rr)->rroriginalttl, (msg)->data + AllowedRRSpace(msg) - TSR_OPT_TOTAL_SPACE)
+
+// Calculate OPT space
+// Assume local variables 'OwnerRecordSpace', 'TraceRecordSpace' & 'tsrOptsCount'
+#define RR_OPT_SPACE    \
+    (OwnerRecordSpace + TraceRecordSpace + TSR_OPT_SPACE +     \
+    ((OwnerRecordSpace || TraceRecordSpace) ? 0 : TSR_OPT_HEADER_SPACE))
+
 // The PutRR_OS variants assume a local variable 'm', put build the packet at m->omsg,
-// and assume local variables 'OwnerRecordSpace' & 'TraceRecordSpace' indicating how many bytes (if any) to reserve to add an OWNER/TRACER option at the end
 #define PutRR_OS_TTL(ptr, count, rr, ttl) \
-    PutResourceRecordTTLWithLimit(&m->omsg, (ptr), (count), (rr), (ttl), m->omsg.data + AllowedRRSpace(&m->omsg) - OwnerRecordSpace - TraceRecordSpace)
+    PutResourceRecordTTLWithLimit(&m->omsg, (ptr), (count), (rr), (ttl), m->omsg.data + AllowedRRSpace(&m->omsg) - RR_OPT_SPACE)
 
 #define PutRR_OS(P, C, RR) PutRR_OS_TTL((P), (C), (RR), (RR)->rroriginalttl)
 
@@ -417,6 +512,12 @@
 extern mDNSBool DNSQuestionCollectsMDNSMetric(const DNSQuestion *q);
 #endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, TERMINUS_ASSISTED_UNICAST_DISCOVERY)
+extern mDNSBool DNSQuestionIsEligibleForMDNSAlternativeService(const DNSQuestion *q);
+extern mDNSBool DNSQuestionRequestsMDNSAlternativeService(const DNSQuestion *q);
+extern mDNSBool DNSQuestionUsesMDNSAlternativeService(const DNSQuestion *q);
+#endif
+
 // ***************************************************************************
 // MARK: - RR List Management & Task Management
 
diff --git a/mDNSCore/mDNS.c b/mDNSCore/mDNS.c
index eecd7da..37ac67c 100644
--- a/mDNSCore/mDNS.c
+++ b/mDNSCore/mDNS.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4; c-file-style: "bsd"; c-basic-offset: 4; fill-column: 108; indent-tabs-mode: nil; -*-
  *
- * Copyright (c) 2002-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -25,6 +25,7 @@
 
 #include "DNSCommon.h"                  // Defines general DNS utility routines
 #include "uDNS.h"                       // Defines entry points into unicast-specific routines
+#include <sys/time.h>                   // For gettimeofday().
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, D2D)
 #include "D2D.h"
@@ -45,13 +46,17 @@
 #if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
 #include "dns_push_discovery.h"
 #include "dns_push_mdns_core.h"
-#include <os/feature_private.h>
 #endif
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
 #include "QuerierSupport.h"
 #endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, UNICAST_ASSIST)
+#include "misc_utilities.h"
+#include "unicast_assist_cache.h"
+#endif
+
 // Disable certain benign warnings with Microsoft compilers
 #if (defined(_MSC_VER))
 // Disable "conditional expression is constant" warning for debug macros.
@@ -67,6 +72,8 @@
     #pragma warning(disable:4706)
 #endif
 
+#include "bsd_queue.h"
+#include "CommonServices.h"
 #include "dns_sd.h" // for kDNSServiceFlags* definitions
 #include "dns_sd_internal.h"
 
@@ -100,16 +107,14 @@
 #include "tls-keychain.h"
 #endif
 
-#if MDNSRESPONDER_SUPPORTS(APPLE, LOG_PRIVACY_LEVEL)
-#include "system_utilities.h" // For is_apple_internal_build().
-#endif
-
 #include "mdns_strict.h"
 
 // Forward declarations
 extern mDNS mDNSStorage; // NOLINT(misc-uninitialized-record-variable): Initialized by mDNS_InitStorage().
 mDNSlocal void BeginSleepProcessing(mDNS *const m);
+#if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
 mDNSlocal void RetrySPSRegistrations(mDNS *const m);
+#endif
 mDNSlocal void SendWakeup(mDNS *const m, mDNSInterfaceID InterfaceID, mDNSEthAddr *EthAddr, mDNSOpaque48 *password, mDNSBool unicastOnly);
 #if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
 mDNSlocal mDNSBool LocalRecordRmvEventsForQuestion(mDNS *const m, DNSQuestion *q);
@@ -169,6 +174,90 @@
 #define DARK_WAKE_DELAY_SLEEP  5
 #define kDarkWakeDelaySleep    (mDNSPlatformOneSecond * DARK_WAKE_DELAY_SLEEP)
 
+struct TSRDataRec {
+    SLIST_ENTRY(TSRDataRec) entries;
+    TSROptData              tsr;
+    domainname              name;
+};
+typedef SLIST_HEAD(, TSRDataRec) TSRDataRecHead;
+mDNSlocal struct TSRDataRec *TSRDataRecCreate(const DNSMessage *const msg, const mDNSu8 *ptr, const mDNSu8 *const end,
+    const rdataOPT * const opt)
+{
+    domainname name;
+    const mDNSu8 *next_ptr;
+    struct TSRDataRec *rec = mDNSNULL;
+
+    next_ptr = getDomainName(msg, ptr, end, &name);
+    mdns_require_action_quiet(next_ptr, exit,
+        LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_ERROR,
+            "TSRDataRecCreate: Bad RR domain name for TSR - tsrTime %d tsrHost %x recIndex %u",
+            opt->u.tsr.timeStamp, opt->u.tsr.hostkeyHash, opt->u.tsr.recIndex));
+
+    rec = (struct TSRDataRec *)mDNSPlatformMemAllocateClear(sizeof(*rec));
+    mdns_require_quiet(rec, exit);
+
+    AssignDomainName(&rec->name, &name);
+    mDNSPlatformMemCopy(&rec->tsr, &opt->u.tsr, sizeof(TSROptData));
+exit:
+    return rec;
+}
+mDNSlocal void TSRDataRecHeadFreeList(TSRDataRecHead *tsrHead)
+{
+    mdns_require_quiet(tsrHead, exit);
+    while (!SLIST_EMPTY(tsrHead)) {
+        struct TSRDataRec *next = SLIST_FIRST(tsrHead);
+        SLIST_REMOVE_HEAD(tsrHead, entries);
+        mDNSPlatformMemFree(next);
+    }
+exit:
+    return;
+}
+
+struct TSRDataPtrRec {
+    SLIST_ENTRY(TSRDataPtrRec)  entries;
+    const TSROptData            *tsr;
+    const domainname            *name;
+};
+typedef SLIST_HEAD(, TSRDataPtrRec) TSRDataPtrRecHead;
+mDNSlocal mDNSBool TSRDataRecPtrHeadAddTSROpt(TSRDataPtrRecHead * const tsrHead, TSROptData * const tsrOpt,
+    const domainname * const name, mDNSu16 index)
+{
+    struct TSRDataPtrRec *rec = (struct TSRDataPtrRec *)mDNSPlatformMemAllocateClear(sizeof(*rec));
+    mdns_require_quiet(rec, exit);
+
+    tsrOpt->recIndex = index;
+    rec->tsr = tsrOpt;
+    rec->name = name;
+    SLIST_INSERT_HEAD(tsrHead, rec, entries);
+    return mDNStrue;
+
+exit:
+    LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_ERROR,
+        "TSRDataRecPtrHeadAddRec: Alloc TSRDataPtrRec failed " PRI_DM_NAME " index %u", DM_NAME_PARAM(name), index);
+    return mDNSfalse;
+}
+mDNSlocal void TSRDataRecPtrHeadRemoveAndFreeFirst(TSRDataPtrRecHead *tsrHead)
+{
+    mdns_require_quiet(tsrHead, exit);
+
+    struct TSRDataPtrRec *first = SLIST_FIRST(tsrHead);
+    SLIST_REMOVE_HEAD(tsrHead, entries);
+    mdns_require_quiet(first, exit);
+
+    mDNSPlatformMemFree(first);
+exit:
+    return;
+}
+mDNSlocal void TSRDataRecPtrHeadFreeList(TSRDataPtrRecHead *tsrHead)
+{
+    mdns_require_quiet(tsrHead, exit);
+    while (!SLIST_EMPTY(tsrHead)) {
+        TSRDataRecPtrHeadRemoveAndFreeFirst(tsrHead);
+    }
+exit:
+    return;
+}
+
 // RFC 6762 defines Passive Observation Of Failures (POOF)
 //
 //    A host observes the multicast queries issued by the other hosts on
@@ -542,40 +631,6 @@
     return(mDNSfalse);
 }
 
-mDNSlocal NetworkInterfaceInfo *FirstInterfaceForID(mDNS *const m, const mDNSInterfaceID InterfaceID)
-{
-    NetworkInterfaceInfo *intf = m->HostInterfaces;
-    while (intf && intf->InterfaceID != InterfaceID) intf = intf->next;
-    return(intf);
-}
-
-mDNSlocal NetworkInterfaceInfo *FirstIPv4LLInterfaceForID(mDNS *const m, const mDNSInterfaceID InterfaceID)
-{
-    NetworkInterfaceInfo *intf;
-
-    if (!InterfaceID)
-        return mDNSNULL;
-
-    // Note: We don't check for InterfaceActive, as the active interface could be IPv6 and
-    // we still want to find the first IPv4 Link-Local interface
-    for (intf = m->HostInterfaces; intf; intf = intf->next)
-    {
-        if (intf->InterfaceID == InterfaceID &&
-            intf->ip.type == mDNSAddrType_IPv4 && mDNSv4AddressIsLinkLocal(&intf->ip.ip.v4))
-        {
-            debugf("FirstIPv4LLInterfaceForID: found LL interface with address %.4a", &intf->ip.ip.v4);
-            return intf;
-        }
-    }
-    return (mDNSNULL);
-}
-
-mDNSexport char *InterfaceNameForID(mDNS *const m, const mDNSInterfaceID InterfaceID)
-{
-    NetworkInterfaceInfo *intf = FirstInterfaceForID(m, InterfaceID);
-    return(intf ? intf->ifname : mDNSNULL);
-}
-
 mDNSlocal mDNSBool FollowCNAME(const DNSQuestion *const q, const ResourceRecord *const rr, const QC_result qcResult)
 {
     // Do not follow CNAME if it is a remove event.
@@ -623,9 +678,8 @@
     DNSQuestion *q;
     if (!m->CurrentQuestion) { LogMsg("GenerateNegativeResponse: ERROR!! CurrentQuestion not set"); return; }
     q = m->CurrentQuestion;
-    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "[R%d->Q%d] GenerateNegativeResponse: Generating negative response for question "
-        PRI_DM_NAME " (" PUB_S ")", q->request_id, mDNSVal16(q->TargetQID), DM_NAME_PARAM(&q->qname),
-        DNSTypeName(q->qtype));
+    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
+        "[Q%d] GenerateNegativeResponse: Generating negative response for question", mDNSVal16(q->TargetQID));
 
     MakeNegativeCacheRecordForQuestion(m, &m->rec.r, q, 60, InterfaceID, responseFlags);
 
@@ -645,14 +699,14 @@
 
 mDNSexport void AnswerQuestionByFollowingCNAME(mDNS *const m, DNSQuestion *q, ResourceRecord *rr)
 {
+    const domainname *const cname = &rr->rdata->u.name;
     const mDNSBool selfref = SameDomainName(&q->qname, &rr->rdata->u.name);
     if (q->CNAMEReferrals >= 10 || selfref)
     {
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "[R%d->Q%d] AnswerQuestionByFollowingCNAME: %p " PRI_DM_NAME " (" PUB_S
-            ")  NOT following CNAME referral %d" PUB_S " for " PRI_S, q->request_id, mDNSVal16(q->TargetQID), q,
-            DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype), q->CNAMEReferrals, selfref ? " (Self-Referential)" : "",
-            RRDisplayString(m, rr));
-
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
+            "[Q%u] AnswerQuestionByFollowingCNAME: Not following CNAME referral -- "
+            "CNAME: " PRI_DM_NAME ", referral count: %u, self referential: " PUB_BOOL,
+            mDNSVal16(q->TargetQID), DM_NAME_PARAM_NONNULL(cname), q->CNAMEReferrals, BOOL_PARAM(selfref));
     }
     else
     {
@@ -679,9 +733,10 @@
         // which we would subsequently cancel and retract if the CNAME referral record were removed.
         // In reality this is such a corner case we'll ignore it until someone actually needs it.
 
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "[R%d->Q%d] AnswerQuestionByFollowingCNAME: %p " PRI_DM_NAME
-            " (" PUB_S ") following CNAME referral %d for " PRI_S, q->request_id, mDNSVal16(q->TargetQID), q,
-            DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype), q->CNAMEReferrals, RRDisplayString(m, rr));
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
+            "[Q%u] AnswerQuestionByFollowingCNAME: following CNAME referral -- "
+            "CNAME: " PRI_DM_NAME ", referral count: %u",
+            mDNSVal16(q->TargetQID), DM_NAME_PARAM_NONNULL(cname), q->CNAMEReferrals);
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, DNS_ANALYTICS)
         metrics = q->metrics;
@@ -705,9 +760,9 @@
         // to try this as unicast query even though it is a .local name
         if (!mDNSOpaque16IsZero(q->TargetQID) && IsLocalDomain(&q->qname))
         {
-            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "[R%d->Q%d] AnswerQuestionByFollowingCNAME: Resolving a .local CNAME %p " PRI_DM_NAME
-                " (" PUB_S ") Record " PRI_S, q->request_id, mDNSVal16(q->TargetQID), q, DM_NAME_PARAM(&q->qname),
-                DNSTypeName(q->qtype), RRDisplayString(m, rr));
+            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
+                "[Q%d] AnswerQuestionByFollowingCNAME: Resolving a .local CNAME -- CNAME: " PRI_DM_NAME,
+                mDNSVal16(q->TargetQID), DM_NAME_PARAM_NONNULL(cname));
             q->IsUnicastDotLocal = mDNStrue;
         }
         q->CNAMEReferrals += 1;                                     // Increment value before calling mDNS_StartQuery_internal
@@ -940,7 +995,7 @@
 
 mDNSlocal mDNSBool ResourceRecordIsValidAnswer(const AuthRecord *const rr)
 {
-    if ((rr->resrec.RecordType & kDNSRecordTypeActiveMask) && rr->resrec.rrtype != kDNSType_TSR &&
+    if ((rr->resrec.RecordType & kDNSRecordTypeActiveMask) &&
         ((rr->Additional1 == mDNSNULL) || (rr->Additional1->resrec.RecordType & kDNSRecordTypeActiveMask)) &&
         ((rr->Additional2 == mDNSNULL) || (rr->Additional2->resrec.RecordType & kDNSRecordTypeActiveMask)) &&
         ((rr->DependentOn == mDNSNULL) || (rr->DependentOn->resrec.RecordType & kDNSRecordTypeActiveMask)))
@@ -1008,7 +1063,7 @@
 #define TimeToAnnounceThisRecord(RR,time) ((RR)->AnnounceCount && (time) - ((RR)->LastAPTime + (RR)->ThisAPInterval) >= 0)
 mDNSexport mDNSs32 RRExpireTime(const CacheRecord *const cr)
 {
-#if MDNSRESPONDER_SUPPORTS(COMMON, DNS_PUSH)
+#if MDNSRESPONDER_SUPPORTS(COMMON, DNS_PUSH) || MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
     return cr->DNSPushSubscribed ? (mDNSStorage.timenow + FutureTime) : (cr->TimeRcvd + TicksTTL(cr));
 #else
     return cr->TimeRcvd + TicksTTL(cr);
@@ -1468,7 +1523,7 @@
     if (RRLocalOnly(rr))
     {
         // A sanity check, this should be prevented in calling code.
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "DecrementAutoTargetServices: called for RRLocalOnly() record: " PUB_S, ARDisplayString(m, rr));
+        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEBUG, "DecrementAutoTargetServices: called for RRLocalOnly() record: " PUB_S, ARDisplayString(m, rr));
         return;
     }
 
@@ -1483,7 +1538,7 @@
             if (AuthRecordIncludesAWDL(rr))
             {
                 m->AutoTargetAWDLIncludedCount--;
-                LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "DecrementAutoTargetServices: AutoTargetAWDLIncludedCount %u Record " PRI_S,
+                LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEBUG, "DecrementAutoTargetServices: AutoTargetAWDLIncludedCount %u Record " PRI_S,
                     m->AutoTargetAWDLIncludedCount, ARDisplayString(m, rr));
                 if (m->AutoTargetAWDLIncludedCount == 0)
                 {
@@ -1494,7 +1549,7 @@
             else
             {
                 m->AutoTargetAWDLOnlyCount--;
-                LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "DecrementAutoTargetServices: AutoTargetAWDLOnlyCount %u Record " PRI_S,
+                LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEBUG, "DecrementAutoTargetServices: AutoTargetAWDLOnlyCount %u Record " PRI_S,
                     m->AutoTargetAWDLOnlyCount, ARDisplayString(m, rr));
                 if ((m->AutoTargetAWDLIncludedCount == 0) && (m->AutoTargetAWDLOnlyCount == 0))
                 {
@@ -1525,7 +1580,7 @@
 #endif
         {
             m->AutoTargetServices--;
-            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "DecrementAutoTargetServices: AutoTargetServices %u Record " PRI_S,
+            LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEBUG, "DecrementAutoTargetServices: AutoTargetServices %u Record " PRI_S,
                 m->AutoTargetServices, ARDisplayString(m, rr));
             if (m->AutoTargetServices == 0)
             {
@@ -1543,7 +1598,7 @@
         if (m->NumAllInterfaceRecords + m->NumAllInterfaceQuestions == 1)
             m->NextBonjourDisableTime = NonZeroTime(m->timenow + (BONJOUR_DISABLE_DELAY * mDNSPlatformOneSecond));
         m->NumAllInterfaceRecords--;
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "DecrementAutoTargetServices: NumAllInterfaceRecords %u NumAllInterfaceQuestions %u " PRI_S,
+        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEBUG, "DecrementAutoTargetServices: NumAllInterfaceRecords %u NumAllInterfaceQuestions %u " PRI_S,
             m->NumAllInterfaceRecords, m->NumAllInterfaceQuestions, ARDisplayString(m, rr));
     }
 #endif
@@ -1565,7 +1620,7 @@
     if (RRLocalOnly(rr))
     {
         // A sanity check, this should be prevented in calling code.
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "IncrementAutoTargetServices: called for RRLocalOnly() record: " PRI_S, ARDisplayString(m, rr));
+        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEBUG, "IncrementAutoTargetServices: called for RRLocalOnly() record: " PRI_S, ARDisplayString(m, rr));
         return;
     }
 
@@ -1573,7 +1628,7 @@
     if (!AuthRecord_uDNS(rr))
     {
         m->NumAllInterfaceRecords++;
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "IncrementAutoTargetServices: NumAllInterfaceRecords %u NumAllInterfaceQuestions %u " PRI_S,
+        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEBUG, "IncrementAutoTargetServices: NumAllInterfaceRecords %u NumAllInterfaceQuestions %u " PRI_S,
             m->NumAllInterfaceRecords, m->NumAllInterfaceQuestions, ARDisplayString(m, rr));
         if (m->NumAllInterfaceRecords + m->NumAllInterfaceQuestions == 1)
         {
@@ -1596,20 +1651,20 @@
         if (AuthRecordIncludesAWDL(rr))
         {
             m->AutoTargetAWDLIncludedCount++;
-            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "IncrementAutoTargetServices: AutoTargetAWDLIncludedCount %u Record " PRI_S,
+            LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEBUG, "IncrementAutoTargetServices: AutoTargetAWDLIncludedCount %u Record " PRI_S,
                 m->AutoTargetAWDLIncludedCount, ARDisplayString(m, rr));
         }
         else if (mDNSPlatformInterfaceIsAWDL(rr->resrec.InterfaceID))
         {
             m->AutoTargetAWDLOnlyCount++;
-            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "IncrementAutoTargetServices: AutoTargetAWDLOnlyCount %u Record " PRI_S,
+            LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEBUG, "IncrementAutoTargetServices: AutoTargetAWDLOnlyCount %u Record " PRI_S,
                 m->AutoTargetAWDLOnlyCount, ARDisplayString(m, rr));
         }
         else
 #endif
         {
             m->AutoTargetServices++;
-            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "IncrementAutoTargetServices: AutoTargetServices %u Record " PRI_S,
+            LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEBUG, "IncrementAutoTargetServices: AutoTargetServices %u Record " PRI_S,
                 m->AutoTargetServices, ARDisplayString(m, rr));
         }
         // If this is the first advertised service and we did not just enable Bonjour above, then
@@ -1652,19 +1707,19 @@
 
     if ((mDNSs32)rr->resrec.rroriginalttl <= 0)
     {
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "mDNS_Register_internal: TTL %X should be 1 - 0x7FFFFFFF " PRI_S, rr->resrec.rroriginalttl,
+        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT, "mDNS_Register_internal: TTL %X should be 1 - 0x7FFFFFFF " PRI_S, rr->resrec.rroriginalttl,
             ARDisplayString(m, rr));
         return(mStatus_BadParamErr);
     }
 
     if (!rr->resrec.RecordType)
     {
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "mDNS_Register_internal: RecordType must be non-zero " PRI_S, ARDisplayString(m, rr));
+        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT, "mDNS_Register_internal: RecordType must be non-zero " PRI_S, ARDisplayString(m, rr));
         return(mStatus_BadParamErr); }
 
     if (m->ShutdownTime)
     {
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "mDNS_Register_internal: Shutting down, can't register " PRI_S, ARDisplayString(m, rr));
+        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT, "mDNS_Register_internal: Shutting down, can't register " PRI_S, ARDisplayString(m, rr));
         return(mStatus_ServiceNotRunning);
     }
 
@@ -1683,7 +1738,7 @@
         }
         if (rr->resrec.InterfaceID != previousID)
         {
-            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "mDNS_Register_internal: Diverting record to local-only " PRI_S, ARDisplayString(m, rr));
+            LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT, "mDNS_Register_internal: Diverting record to local-only " PRI_S, ARDisplayString(m, rr));
         }
     }
 
@@ -1691,7 +1746,7 @@
     {
         if (CheckAuthSameRecord(&m->rrauth, rr))
         {
-            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "mDNS_Register_internal: ERROR!! Tried to register LocalOnly AuthRecord %p "
+            LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT, "mDNS_Register_internal: ERROR!! Tried to register LocalOnly AuthRecord %p "
                 PRI_DM_NAME " (" PUB_S ") that's already in the list",
                 rr, DM_NAME_PARAM(rr->resrec.name), DNSTypeName(rr->resrec.rrtype));
             return(mStatus_AlreadyRegistered);
@@ -1702,7 +1757,7 @@
         while (*p && *p != rr) p=&(*p)->next;
         if (*p)
         {
-            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "mDNS_Register_internal: ERROR!! Tried to register AuthRecord %p "
+            LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT, "mDNS_Register_internal: ERROR!! Tried to register AuthRecord %p "
                 PRI_DM_NAME " (" PUB_S ") that's already in the list",
                 rr, DM_NAME_PARAM(rr->resrec.name), DNSTypeName(rr->resrec.rrtype));
             return(mStatus_AlreadyRegistered);
@@ -1712,7 +1767,7 @@
     while (*d && *d != rr) d=&(*d)->next;
     if (*d)
     {
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "mDNS_Register_internal: ERROR!! Tried to register AuthRecord %p "
+        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT, "mDNS_Register_internal: ERROR!! Tried to register AuthRecord %p "
             PRI_DM_NAME " (" PUB_S ") that's already in the Duplicate list",
             rr, DM_NAME_PARAM(rr->resrec.name), DNSTypeName(rr->resrec.rrtype));
         return(mStatus_AlreadyRegistered);
@@ -1724,14 +1779,14 @@
             rr->resrec.RecordType =  kDNSRecordTypeVerified;
         else if (rr->resrec.RecordType != kDNSRecordTypeKnownUnique)
         {
-            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "mDNS_Register_internal: ERROR! " PRI_DM_NAME " (" PUB_S
+            LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT, "mDNS_Register_internal: ERROR! " PRI_DM_NAME " (" PUB_S
                 "): rr->DependentOn && RecordType != kDNSRecordTypeUnique or kDNSRecordTypeKnownUnique",
                 DM_NAME_PARAM(rr->resrec.name), DNSTypeName(rr->resrec.rrtype));
             return(mStatus_Invalid);
         }
         if (!(rr->DependentOn->resrec.RecordType & (kDNSRecordTypeUnique | kDNSRecordTypeVerified | kDNSRecordTypeKnownUnique)))
         {
-            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "mDNS_Register_internal: ERROR! " PRI_DM_NAME " (" PUB_S
+            LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT, "mDNS_Register_internal: ERROR! " PRI_DM_NAME " (" PUB_S
                 "): rr->DependentOn->RecordType bad type %X",
                 DM_NAME_PARAM(rr->resrec.name), DNSTypeName(rr->resrec.rrtype), rr->DependentOn->resrec.RecordType);
             return(mStatus_Invalid);
@@ -1821,13 +1876,6 @@
     // since RFC 1035 specifies a TXT record as "One or more <character-string>s", not "Zero or more <character-string>s".
     // Since some legacy apps try to create zero-length TXT records, we'll silently correct it here.
     if (rr->resrec.rrtype == kDNSType_TXT && rr->resrec.rdlength == 0) { rr->resrec.rdlength = 1; rr->resrec.rdata->u.txt.c[0] = 0; }
-    // No need to announce or probe for the TSR record since it's an auxiliary record.
-    if (rr->resrec.rrtype == kDNSType_TSR)
-    {
-        rr->AnnounceCount = 0;
-        rr->ProbeCount = 0;
-    }
-
     if (rr->AutoTarget)
     {
         SetTargetToHostName(m, rr); // Also sets rdlength and rdestimate for us, and calls InitializeLastAPTime();
@@ -1839,7 +1887,7 @@
             // Initialize the target so that we don't crash while logging etc.
             domainname *tar = GetRRDomainNameTarget(&rr->resrec);
             if (tar) tar->c[0] = 0;
-            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "mDNS_Register_internal: record " PUB_S " in NoTarget state", ARDisplayString(m, rr));
+            LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT, "mDNS_Register_internal: record " PUB_S " in NoTarget state", ARDisplayString(m, rr));
         }
 #endif
     }
@@ -1851,14 +1899,14 @@
 
     if (!ValidateDomainName(rr->resrec.name))
     {
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "Attempt to register record with invalid name: " PRI_S, ARDisplayString(m, rr));
+        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT, "Attempt to register record with invalid name: " PRI_S, ARDisplayString(m, rr));
         return(mStatus_Invalid);
     }
 
     // Don't do this until *after* we've set rr->resrec.rdlength
     if (!ValidateRData(rr->resrec.rrtype, rr->resrec.rdlength, rr->resrec.rdata))
     {
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "Attempt to register record with invalid rdata: " PRI_S, ARDisplayString(m, rr));
+        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT, "Attempt to register record with invalid rdata: " PRI_S, ARDisplayString(m, rr));
         return(mStatus_Invalid);
     }
 
@@ -1877,7 +1925,7 @@
         {
             if (CheckAuthRecordConflict(&m->rrauth, rr))
             {
-                LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "mDNS_Register_internal: Name conflict " PRI_S " (%p), InterfaceID %p",
+                LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT, "mDNS_Register_internal: Name conflict " PRI_S " (%p), InterfaceID %p",
                     ARDisplayString(m, rr), rr, rr->resrec.InterfaceID);
                 return mStatus_NameConflict;
             }
@@ -1921,11 +1969,12 @@
     }
 
     const domainname *const rrName = rr->resrec.name;
-    const mDNSu32 nameHash = mDNS_NonCryptoHash(mDNSNonCryptoHash_FNV1a, rrName->c, DomainNameLength(rrName));
+    const mDNSu32 nameHash = mDNS_DomainNameFNV1aHash(rrName);
     if (r)
     {
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "mDNS_Register_internal: Adding to duplicate list "
-            " name hash: %x " PRI_S, nameHash, ARDisplayString(m,rr));
+        MDNS_CORE_LOG_RDATA(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT, &((rr)->resrec),
+            "mDNS_Register_internal: adding to duplicate list -- name: " PRI_DM_NAME " (%x), ",
+            DM_NAME_PARAM(rrName), nameHash);
         *d = rr;
         // If the previous copy of this record is already verified unique,
         // then indicate that we should move this record promptly to kDNSRecordTypeUnique state.
@@ -1936,8 +1985,9 @@
     }
     else
     {
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "mDNS_Register_internal: Adding to active record list --"
-            " name hash: %x " PRI_S, nameHash, ARDisplayString(m,rr));
+        MDNS_CORE_LOG_RDATA(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT, &((rr)->resrec),
+            "mDNS_Register_internal: adding to active record list -- name: " PRI_DM_NAME " (%x), ",
+            DM_NAME_PARAM(rrName), nameHash);
         if (RRLocalOnly(rr))
         {
             AuthGroup *ag;
@@ -2021,26 +2071,77 @@
         rr->UpdateCallback(m, rr, OldRData, OldRDLen);          // ... and let the client know
 }
 
-mDNSlocal AuthRecord *FindOrphanedTSR(mDNS *const m, const mDNSInterfaceID interfaceID, const mDNSu16 rrClass,
-    const mDNSu32 nameHash, const domainname *const name)
+mDNSexport mDNSBool getValidContinousTSRTime(mDNSs32 *timestampContinuous, mDNSu32 tsrTimestamp)
 {
-    AuthRecord *tsr = mDNSNULL;
-
-    for (AuthRecord *ar = m->ResourceRecords; ar && !tsr; ar = ar->next)
+    if (tsrTimestamp <= MaxTimeSinceReceived)
     {
-        const ResourceRecord *const rr = &ar->resrec;
-        if ((rr->rrtype == kDNSType_TSR) && (rr->InterfaceID == interfaceID) &&
-            (rr->rrclass == rrClass) && (rr->namehash == nameHash) && (SameDomainName(rr->name, name)))
+        *timestampContinuous = mDNSPlatformContinuousTimeSeconds() - (mDNSs32)tsrTimestamp;
+        return mDNStrue;
+    }
+    return mDNSfalse;
+}
+
+mDNSlocal AuthRecord *mDNSGetTSRForAuthRecordNamed(mDNS *const m, const domainname * const name, const mDNSu32 namehash)
+{
+    AuthRecord *ar = mDNSNULL;
+    for (ar = m->ResourceRecords; ar; ar = ar->next)
+    {
+        if (ar->resrec.rrtype == kDNSType_OPT &&
+            ar->resrec.namehash == namehash &&
+            SameDomainName(ar->resrec.name, name))
         {
-            tsr = ar;
+            const rdataOPT *const opt = (const rdataOPT *)&ar->resrec.rdata->u.data[0];
+            if (opt->opt != kDNSOpt_TSR)
+            {
+                ar = mDNSNULL;
+                LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
+                    "mDNSGetTSRForAuthRecordNamed: Found OPT that is not kDNSOpt_TSR (%d)", opt->opt);
+            }
+            break;
         }
     }
+    return ar;
+}
+
+mDNSexport AuthRecord *mDNSGetTSRForAuthRecord(mDNS *const m, const AuthRecord *const rr)
+{
+    return mDNSGetTSRForAuthRecordNamed(m, rr->resrec.name, rr->resrec.namehash);
+}
+
+mDNSexport CacheRecord *mDNSGetTSRForCacheGroup(const CacheGroup *const cg)
+{
+    CacheRecord *rr;
+    for (rr = cg ? cg->members : mDNSNULL; rr; rr=rr->next)
+    {
+        if (rr->resrec.rrtype == kDNSType_OPT)
+        {
+            const rdataOPT *const opt = (const rdataOPT *)&rr->resrec.rdata->u.data[0];
+            if (opt->opt == kDNSOpt_TSR)
+            {
+                return rr;
+            }
+            else
+            {
+                LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
+                    "mDNSGetTSRForCacheGroup: Found OPT that is not kDNSOpt_TSR (%d)", opt->opt);
+                break;
+            }
+        }
+    }
+    return mDNSNULL;
+}
+
+mDNSlocal AuthRecord *FindOrphanedTSR(mDNS *const m, const domainname *const name, const mDNSu32 namehash)
+{
+    AuthRecord *tsr = mDNSGetTSRForAuthRecordNamed(m, name, namehash);
 
     for (const AuthRecord *ar = m->ResourceRecords; ar && tsr ; ar = ar->next)
     {
-        if (ar->resrec.rrtype != kDNSType_TSR && SameResourceRecordNameClassInterface(ar, tsr))
+        if (ar->resrec.rrtype != kDNSType_OPT && 
+            ar->resrec.namehash == namehash &&
+            SameDomainName(ar->resrec.name, name))
         {
-            // There is at least one non-TSR record that has the same name, class and interface index with the TSR.
+            // There is at least one non-TSR record that has the same name.
             // So this TSR is not an orphan.
             tsr = mDNSNULL;
         }
@@ -2049,6 +2150,86 @@
     return tsr;
 }
 
+mDNSlocal void SetupTSROpt(const TSROptData *tsrData, rdataOPT *const tsrOPT)
+{
+    tsrOPT->u.tsr.hostkeyHash   = tsrData->hostkeyHash;
+    tsrOPT->u.tsr.recIndex      = tsrData->recIndex;
+    tsrOPT->u.tsr.timeStamp     = tsrData->timeStamp;
+
+    tsrOPT->opt              = kDNSOpt_TSR;
+    tsrOPT->optlen           = DNSOpt_TSRData_Space - 4;
+}
+
+mDNSlocal mDNSu8 *AddTSRROptsToMessage(const TSRDataPtrRecHead * const tsrHead, const DNSMessage *const msg,
+    mDNSu8 * const rdlengthptr, mDNSu8 *ptr, const mDNSu8 *end)
+{
+    RData rdatastorage = {sizeof(RDataBody), 0, {0}};
+    ResourceRecord next_opt;
+    mDNSu8 *startptr = ptr;
+    mDNSu16 actualLength = (mDNSu16)(rdlengthptr[0] << 8) + (mDNSu16)rdlengthptr[1];
+    next_opt.rrtype     = kDNSType_OPT;
+    next_opt.rdata      = &rdatastorage;
+    next_opt.rdlength   = sizeof(rdataOPT);
+    struct TSRDataPtrRec *next;
+    SLIST_FOREACH(next, tsrHead, entries)
+    {
+        SetupTSROpt(next->tsr, &next_opt.rdata->u.opt[0]);
+        ptr = putRData(msg, ptr, end, &next_opt);
+        if (!ptr)
+        {
+            LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_ERROR,
+                "AddTSRRDataToMessage: TSR can't be written -- name " PRI_DM_NAME " hashkey %x",
+                DM_NAME_PARAM(next->name), next->tsr->hostkeyHash);
+            break;
+        }
+    }
+    if (ptr && startptr != ptr)
+    {
+        actualLength += ptr - startptr;
+        rdlengthptr[0] = (mDNSu8)(actualLength  >> 8);
+        rdlengthptr[1] = (mDNSu8)(actualLength  &  0xFF);
+    }
+    return ptr;
+}
+
+
+mDNSlocal const TSROptData *TSRForNameFromDataRec(TSRDataRecHead *const tsrHead, const domainname *const name)
+{
+    struct TSRDataRec *nextTSR;
+    SLIST_FOREACH(nextTSR, tsrHead, entries)
+    {
+        if (SameDomainName(&nextTSR->name, name))
+        {
+            return &nextTSR->tsr;
+        }
+    }
+    return mDNSNULL;
+}
+
+mDNSlocal const TSROptData *TSRPtrForNameFromDataPtrRec(TSRDataPtrRecHead *const tsrHead, const domainname *const name)
+{
+    struct TSRDataPtrRec *nextTSR;
+    SLIST_FOREACH(nextTSR, tsrHead, entries)
+    {
+        if (SameDomainName(nextTSR->name, name))
+        {
+            return nextTSR->tsr;
+        }
+    }
+    return mDNSNULL;
+}
+
+mDNSlocal TSROptData *TSROptGetIfNew(mDNS *const m, const AuthRecord *const rr, TSRDataPtrRecHead * const tsrHead)
+{
+    AuthRecord *tsrOptRecord = mDNSGetTSRForAuthRecord(m, rr);
+    if (tsrOptRecord    &&
+        !TSRPtrForNameFromDataPtrRec(tsrHead, rr->resrec.name))
+    {
+        return &tsrOptRecord->resrec.rdata->u.opt[0].u.tsr;
+    }
+    return mDNSNULL;
+}
+
 // Note: mDNS_Deregister_internal can call a user callback, which may change the record list and/or question list.
 // Any code walking either list must use the CurrentQuestion and/or CurrentRecord mechanism to protect against this.
 // Exported so uDNS.c can call this
@@ -2059,12 +2240,11 @@
     AuthRecord **p = &m->ResourceRecords;   // Find this record in our list of active records
     mDNSBool dupList = mDNSfalse;
 
-    const mDNSInterfaceID interfeceIDToMatchTSR = rr->resrec.InterfaceID;
-    const mDNSu16 rrClassToMatchTSR = rr->resrec.rrclass;
+    const CacheGroup *cg = mDNSNULL;
     const mDNSu32 nameHashToMatchTSR = rr->resrec.namehash;
     domainname nameToMatchTSR;
     AssignDomainName(&nameToMatchTSR, rr->resrec.name);
-    const mDNSBool isTSR = (rr->resrec.rrtype == kDNSType_TSR);
+    const mDNSBool isTSR = (rr->resrec.rrtype == kDNSType_OPT);
 
     if (RRLocalOnly(rr))
     {
@@ -2091,6 +2271,15 @@
             // deregistering rr. We need to do this scan *before* we give the client the chance to free and reuse the rr memory.
             for (r2 = m->DuplicateRecords; r2; r2=r2->next) if (RecordIsLocalDuplicate(r2, rr)) r2->ProbeCount = 0xFF;
         }
+        else if (drt == mDNS_Dereg_stale)     // If this was stale data, see that all duplicates on all interfaces get the same treatment
+        {
+            mDNSInterfaceID storeID = rr->resrec.InterfaceID;
+            rr->resrec.InterfaceID = kDNSServiceInterfaceIndexAny;
+            // Scan for duplicates of rr, and mark them for deregistration at the end of this routine, after we've finished
+            // deregistering rr. We need to do this scan *before* we give the client the chance to free and reuse the rr memory.
+            for (r2 = m->DuplicateRecords; r2; r2=r2->next) if (RecordIsLocalDuplicate(r2, rr)) r2->ProbeCount = 0xFF;
+            rr->resrec.InterfaceID = storeID;
+        }
         else
         {
             // Before we delete the record (and potentially send a goodbye packet)
@@ -2108,7 +2297,7 @@
                     dup->next = mDNSNULL;
                     if (!InsertAuthRecord(m, &m->rrauth, dup))
                     {
-                        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "mDNS_Deregister_internal: ERROR!! cannot insert " PRI_S, ARDisplayString(m, dup));
+                        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT, "mDNS_Deregister_internal: ERROR!! cannot insert " PRI_S, ARDisplayString(m, dup));
                     }
                 }
                 else
@@ -2166,7 +2355,7 @@
         // No need to log an error message if we already know this is a potentially repeated deregistration
         if (drt != mDNS_Dereg_repeat)
         {
-            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "mDNS_Deregister_internal: Record %p not found in list " PRI_S, rr, ARDisplayString(m,rr));
+            LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT, "mDNS_Deregister_internal: Record %p not found in list " PRI_S, rr, ARDisplayString(m,rr));
         }
         return(mStatus_BadReferenceErr);
     }
@@ -2224,12 +2413,12 @@
 
     if      (RecordType == kDNSRecordTypeUnregistered)
     {
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "mDNS_Deregister_internal: " PRI_S " already marked kDNSRecordTypeUnregistered",
+        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT, "mDNS_Deregister_internal: " PRI_S " already marked kDNSRecordTypeUnregistered",
             ARDisplayString(m, rr));
     }
     else if (RecordType == kDNSRecordTypeDeregistering)
     {
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "mDNS_Deregister_internal: " PRI_S " already marked kDNSRecordTypeDeregistering",
+        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT, "mDNS_Deregister_internal: " PRI_S " already marked kDNSRecordTypeDeregistering",
             ARDisplayString(m, rr));
         return(mStatus_BadReferenceErr);
     }
@@ -2250,6 +2439,33 @@
     }
     else
     {
+        // If the AuthRecord isn't a duplicate, isn't LocalOnly, and is unique, then flush each cached record that was
+        // received via an interface that applies to the AuthRecord and whose name, type, class, and record data matches
+        // that of the AuthRecord.
+        //
+        // Some clients are counting on fully deregistered records to no longer be present in the record cache (see
+        // rdar://121145674).
+        //
+        // Notes:
+        // 1. LocalOnly AuthRecords don't cause any mDNS traffic, so their records never populate the record cache.
+        // 2. Shared AuthRecords, unlike unique AuthRecords, send out goodbye packets when deregistered, which causes
+        //    their cached copies to be purged from record caches including this mDNSResponder's record cache.
+        if (!dupList && !RRLocalOnly(rr) && (rr->resrec.RecordType & kDNSRecordTypeUniqueMask))
+        {
+            cg = CacheGroupForRecord(m, &rr->resrec);
+            for (CacheRecord *cr = cg ? cg->members : mDNSNULL; cr; cr = cr->next)
+            {
+                const mDNSInterfaceID interface = cr->resrec.InterfaceID;
+                if (IsInterfaceValidForAuthRecord(rr, interface) && IdenticalSameNameRecord(&cr->resrec, &rr->resrec))
+                {
+                    LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEBUG,
+                        "mDNS_Deregister_internal: Purging cached record that matches deregistered AuthRecord -- "
+                        "interface: " PUB_S "/%u, record: " PRI_S,
+                        InterfaceNameForIDOrEmptyString(interface), IIDPrintable(interface), CRDisplayString(m, cr));
+                    mDNS_PurgeCacheResourceRecord(m, cr);
+                }
+            }
+        }
         if (!dupList && RRLocalOnly(rr))
         {
             AuthGroup *ag = RemoveAuthRecord(m, &m->rrauth, rr);
@@ -2268,7 +2484,7 @@
         verbosedebugf("mDNS_Deregister_internal: Deleting record for %s", ARDisplayString(m, rr));
         rr->resrec.RecordType = kDNSRecordTypeUnregistered;
 
-        if ((drt == mDNS_Dereg_conflict || drt == mDNS_Dereg_repeat) && RecordType == kDNSRecordTypeShared)
+        if ((drt == mDNS_Dereg_conflict || drt == mDNS_Dereg_stale || drt == mDNS_Dereg_repeat) && RecordType == kDNSRecordTypeShared)
             debugf("mDNS_Deregister_internal: Cannot have a conflict on a shared record! %##s (%s)",
                    rr->resrec.name->c, DNSTypeName(rr->resrec.rrtype));
 
@@ -2280,20 +2496,21 @@
         // is allowed to do anything, including starting/stopping queries, registering/deregistering records, etc.
         // In this case the likely client action to the mStatus_MemFree message is to free the memory,
         // so any attempt to touch rr after this is likely to lead to a crash.
-        if (drt != mDNS_Dereg_conflict)
+        if (drt != mDNS_Dereg_conflict && drt != mDNS_Dereg_stale)
         {
             mDNS_DropLockBeforeCallback();      // Allow client to legally make mDNS API calls from the callback
-            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "mDNS_Deregister_internal: callback with mStatus_MemFree for " PRI_S, ARDisplayString(m, rr));
+            LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT, "mDNS_Deregister_internal: callback with mStatus_MemFree for " PRI_S, ARDisplayString(m, rr));
             if (rr->RecordCallback)
                 rr->RecordCallback(m, rr, mStatus_MemFree);         // MUST NOT touch rr after this
             mDNS_ReclaimLockAfterCallback();    // Decrement mDNS_reentrancy to block mDNS API calls again
         }
         else
         {
+            const mStatus status_result = (drt == mDNS_Dereg_conflict) ? mStatus_NameConflict : mStatus_StaleData;
             RecordProbeFailure(m, rr);
             mDNS_DropLockBeforeCallback();      // Allow client to legally make mDNS API calls from the callback
             if (rr->RecordCallback)
-                rr->RecordCallback(m, rr, mStatus_NameConflict);    // MUST NOT touch rr after this
+                rr->RecordCallback(m, rr, status_result);    // MUST NOT touch rr after this
             mDNS_ReclaimLockAfterCallback();    // Decrement mDNS_reentrancy to block mDNS API calls again
             // Now that we've finished deregistering rr, check our DuplicateRecords list for any that we marked previously.
             // Note that with all the client callbacks going on, by the time we get here all the
@@ -2311,7 +2528,7 @@
                     // See if this record was also registered with any D2D plugins.
                     D2D_stop_advertising_record(r2);
 #endif
-                    mDNS_Deregister_internal(m, r2, mDNS_Dereg_conflict);
+                    mDNS_Deregister_internal(m, r2, status_result);
                     // As this is a duplicate record, it will be unlinked from the list
                     // immediately
                     r2 = m->DuplicateRecords;
@@ -2323,15 +2540,25 @@
 
     // Find the corresponding TSR after we have finished the clean process.
     AuthRecord *const tsr = isTSR ? NULL :
-        FindOrphanedTSR(m, interfeceIDToMatchTSR, rrClassToMatchTSR, nameHashToMatchTSR, &nameToMatchTSR);
+        FindOrphanedTSR(m, &nameToMatchTSR, nameHashToMatchTSR);
 
     // When the last record sharing the same with the TSR record was deregistered, we should deregister the TSR record.
     if (tsr)
     {
-        const RDataBody2 *const rdb = (RDataBody2 *)tsr->resrec.rdata->u.data;
-        LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT, "Removing orphaned TSR - name: " PRI_DM_NAME ", timestamp: %d, ptr: %p",
-                  DM_NAME_PARAM(tsr->resrec.name), rdb->tsr_value, tsr);
+        LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT, 
+            "Deregistering orphaned TSR - " PRI_S, ARDisplayString(m, tsr));
         mDNS_Deregister_internal(m, tsr, mDNS_Dereg_repeat);
+
+        if (cg)
+        {   // Also remove associated TSR cache record
+            CacheRecord *cacheTSR = mDNSGetTSRForCacheGroup(cg);
+            if (cacheTSR)
+            {
+                LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEBUG,
+                    "Purging cached TSR record that matches orphaned TSR -- " PRI_S, CRDisplayString(m, cacheTSR));
+                mDNS_PurgeCacheResourceRecord(m, cacheTSR);
+            }
+        }
     }
 
     return(mStatus_NoError);
@@ -2342,8 +2569,8 @@
 
 mDNSlocal void AddRecordToResponseList(AuthRecord ***nrpp, AuthRecord *rr, AuthRecord *add)
 {
-    // Add the record if it hasn't already been added. Never add TSR records; these only appear in probes.
-    if (rr->NextResponse == mDNSNULL && *nrpp != &rr->NextResponse && rr->resrec.rrtype != kDNSType_TSR)
+    // Add the record if it hasn't already been added.
+    if (rr->NextResponse == mDNSNULL && *nrpp != &rr->NextResponse)
     {
         **nrpp = rr;
         // NR_AdditionalTo must point to a record with NR_AnswerTo set (and not NR_AdditionalTo)
@@ -2470,6 +2697,9 @@
 
     while (ResponseRecords)
     {
+        TSRDataPtrRecHead tsrOpts = SLIST_HEAD_INITIALIZER(tsrOpts);
+        TSROptData *newTSROpt;
+        mDNSu16 tsrOptsCount = 0;
         mDNSu8 *responseptr = m->omsg.data;
         mDNSu8 *newptr;
         InitializeDNSMessage(&m->omsg.h, zeroID, ResponseFlags);
@@ -2481,8 +2711,13 @@
             if (rr->resrec.RecordType & kDNSRecordTypeUniqueMask)
                 rr->resrec.rrclass |= kDNSClass_UniqueRRSet;        // Temporarily set the cache flush bit so PutResourceRecord will set it
 
-            newptr = PutResourceRecord(&m->omsg, responseptr, &m->omsg.h.numAnswers, &rr->resrec);
-
+            if ((newTSROpt = TSROptGetIfNew(m, rr, &tsrOpts)) != mDNSNULL) tsrOptsCount++;
+            newptr = PutResourceRecordTSR(&m->omsg, responseptr, &m->omsg.h.numAnswers, &rr->resrec);
+            if (newTSROpt)
+            {
+                if (newptr) TSRDataRecPtrHeadAddTSROpt(&tsrOpts, newTSROpt, rr->resrec.name, m->omsg.h.numAnswers - 1);
+                else        tsrOptsCount--;
+            }
             rr->resrec.rrclass &= ~kDNSClass_UniqueRRSet;           // Make sure to clear cache flush bit back to normal state
             if (!newptr && m->omsg.h.numAnswers)
             {
@@ -2502,9 +2737,14 @@
             rr = ResponseRecords;
             if (rr->resrec.RecordType & kDNSRecordTypeUniqueMask)
                 rr->resrec.rrclass |= kDNSClass_UniqueRRSet;        // Temporarily set the cache flush bit so PutResourceRecord will set it
-            newptr = PutResourceRecord(&m->omsg, responseptr, &m->omsg.h.numAdditionals, &rr->resrec);
+            if ((newTSROpt = TSROptGetIfNew(m, rr, &tsrOpts)) != mDNSNULL) tsrOptsCount++;
+            newptr = PutResourceRecordTSR(&m->omsg, responseptr, &m->omsg.h.numAdditionals, &rr->resrec);
+            if (newTSROpt)
+            {
+                if (newptr) TSRDataRecPtrHeadAddTSROpt(&tsrOpts, newTSROpt, rr->resrec.name, m->omsg.h.numAnswers + m->omsg.h.numAdditionals - 1);
+                else        tsrOptsCount--;
+            }
             rr->resrec.rrclass &= ~kDNSClass_UniqueRRSet;           // Make sure to clear cache flush bit back to normal state
-
             if (newptr) responseptr = newptr;
             if (newptr && m->omsg.h.numAnswers) rr->RequireGoodbye = mDNStrue;
             else if (rr->resrec.RecordType & kDNSRecordTypeUniqueMask) rr->ImmedAnswer = mDNSInterfaceMark;
@@ -2515,7 +2755,44 @@
         }
 
         if (m->omsg.h.numAnswers)
+        {
+            if (!SLIST_EMPTY(&tsrOpts))
+            {
+                mDNSu8 *saveptr;
+                AuthRecord opt;
+                mDNS_SetupResourceRecord(&opt, mDNSNULL, mDNSInterface_Any, kDNSType_OPT, kStandardTTL, kDNSRecordTypeKnownUnique, AuthRecordAny, mDNSNULL, mDNSNULL);
+                opt.resrec.rrclass    = NormalMaxDNSMessageData;
+                opt.resrec.rdlength   = 0;
+                opt.resrec.rdestimate = 0;
+                if (!SLIST_EMPTY(&tsrOpts))
+                {
+                    opt.resrec.rdlength   += sizeof(rdataOPT);
+                    opt.resrec.rdestimate += sizeof(rdataOPT);
+                    SetupTSROpt(SLIST_FIRST(&tsrOpts)->tsr, &opt.resrec.rdata->u.opt[0]);
+                    TSRDataRecPtrHeadRemoveAndFreeFirst(&tsrOpts);
+                }
+                // Put record after first TSR
+                saveptr = responseptr;
+                newptr = PutResourceRecord(&m->omsg, responseptr, &m->omsg.h.numAdditionals, &opt.resrec);
+                if (newptr && !SLIST_EMPTY(&tsrOpts))
+                {
+                    mDNSu8 *rdlengthptr = saveptr + 2 + 2 + 4 + 1; // rrtype, rrclass, ttl, 0-length name
+                    newptr = AddTSRROptsToMessage(&tsrOpts, &m->omsg, rdlengthptr, newptr,
+                        m->omsg.data + AllowedRRSpace(&m->omsg));
+                }
+                if (newptr)
+                {
+                    responseptr = newptr;
+                }
+                else
+                {
+                    LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_ERROR, "SendDelayedUnicastResponse: How did we fail to have space for OPT record (%d/%d/%d/%d) %s",
+                        m->omsg.h.numQuestions, m->omsg.h.numAnswers, m->omsg.h.numAuthorities, m->omsg.h.numAdditionals, ARDisplayString(m, &opt));
+                }
+            }
             mDNSSendDNSMessage(m, &m->omsg, responseptr, InterfaceID, mDNSNULL, mDNSNULL, dest, MulticastDNSPort, mDNSNULL, mDNSfalse);
+        }
+        TSRDataRecPtrHeadFreeList(&tsrOpts);
     }
 }
 
@@ -2839,6 +3116,21 @@
     }
 }
 
+mDNSlocal mDNSu32 DetermineOwnerRecordSpace(const NetworkInterfaceInfo *const intf)
+{
+    mDNSu32 space = 0;
+#if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
+    mDNS *const m = &mDNSStorage;
+    if (m->AnnounceOwner && intf->MAC.l[0])
+    {
+        space = DNSOpt_Header_Space + DNSOpt_Owner_Space(&m->PrimaryMAC, &intf->MAC);
+    }
+#else
+    (void)intf;
+#endif
+    return space;
+}
+
 // Note about acceleration of announcements to facilitate automatic coalescing of
 // multiple independent threads of announcements into a single synchronized thread:
 // The announcements in the packet may be at different stages of maturity;
@@ -2864,7 +3156,9 @@
 
     m->NextScheduledResponse = m->timenow + FutureTime;
 
+#if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
     if (m->SleepState == SleepState_Transferring) RetrySPSRegistrations(m);
+#endif
 
     for (rr = m->ResourceRecords; rr; rr=rr->next)
         if (rr->ImmedUnicast)
@@ -3066,8 +3360,11 @@
 
     while (intf)
     {
-        int OwnerRecordSpace = (m->AnnounceOwner && intf->MAC.l[0]) ? DNSOpt_Header_Space + DNSOpt_Owner_Space(&m->PrimaryMAC, &intf->MAC) : 0;
+        const mDNSu32 OwnerRecordSpace = DetermineOwnerRecordSpace(intf);
         int TraceRecordSpace = (mDNS_McastTracingEnabled && MDNS_TRACER) ? DNSOpt_Header_Space + DNSOpt_TraceData_Space : 0;
+        TSRDataPtrRecHead tsrOpts = SLIST_HEAD_INITIALIZER(tsrOpts);
+        TSROptData *newTSROpt;
+        mDNSu16 tsrOptsCount = 0;
         int numDereg    = 0;
         int numAnnounce = 0;
         int numAnswer   = 0;
@@ -3101,7 +3398,13 @@
                     // See if we should send a courtesy "goodbye" for the old data before we replace it.
                     if (ResourceRecordIsValidAnswer(rr) && rr->resrec.RecordType == kDNSRecordTypeShared && rr->RequireGoodbye)
                     {
+                        if ((newTSROpt = TSROptGetIfNew(m, rr, &tsrOpts)) != mDNSNULL) tsrOptsCount++;
                         newptr = PutRR_OS_TTL(responseptr, &m->omsg.h.numAnswers, &rr->resrec, 0);
+                        if (newTSROpt)
+                        {
+                            if (newptr) TSRDataRecPtrHeadAddTSROpt(&tsrOpts, newTSROpt, rr->resrec.name, m->omsg.h.numAnswers - 1);
+                            else        tsrOptsCount--;
+                        }
                         if (newptr) { responseptr = newptr; numDereg++; rr->RequireGoodbye = mDNSfalse; }
                         else continue; // If this packet is already too full to hold the goodbye for this record, skip it for now and we'll retry later
                     }
@@ -3110,7 +3413,13 @@
 
                 if (rr->resrec.RecordType & kDNSRecordTypeUniqueMask)
                     rr->resrec.rrclass |= kDNSClass_UniqueRRSet;        // Temporarily set the cache flush bit so PutResourceRecord will set it
+                if ((newTSROpt = TSROptGetIfNew(m, rr, &tsrOpts)) != mDNSNULL) tsrOptsCount++;
                 newptr = PutRR_OS_TTL(responseptr, &m->omsg.h.numAnswers, &rr->resrec, active ? rr->resrec.rroriginalttl : 0);
+                if (newTSROpt)
+                {
+                    if (newptr) TSRDataRecPtrHeadAddTSROpt(&tsrOpts, newTSROpt, rr->resrec.name, m->omsg.h.numAnswers - 1);
+                    else        tsrOptsCount--;
+                }
                 rr->resrec.rrclass &= ~kDNSClass_UniqueRRSet;           // Make sure to clear cache flush bit back to normal state
                 if (newptr)
                 {
@@ -3170,7 +3479,13 @@
 
                         if (rr->resrec.RecordType & kDNSRecordTypeUniqueMask)
                             rr->resrec.rrclass |= kDNSClass_UniqueRRSet;    // Temporarily set the cache flush bit so PutResourceRecord will set it
+                        if ((newTSROpt = TSROptGetIfNew(m, rr, &tsrOpts)) != mDNSNULL) tsrOptsCount++;;
                         newptr = PutRR_OS(newptr, &m->omsg.h.numAdditionals, &rr->resrec);
+                        if (newTSROpt)
+                        {
+                            if (newptr) TSRDataRecPtrHeadAddTSROpt(&tsrOpts, newTSROpt, rr->resrec.name, m->omsg.h.numAnswers + m->omsg.h.numAdditionals - 1);
+                            else        tsrOptsCount--;
+                        }
                         rr->resrec.rrclass &= ~kDNSClass_UniqueRRSet;       // Make sure to clear cache flush bit back to normal state
                         if (newptr)
                         {
@@ -3219,7 +3534,13 @@
                     newptr = responseptr;
                     if (!r2)    // If we successfully built our NSEC record, add it to the packet now
                     {
+                        if ((newTSROpt = TSROptGetIfNew(m, rr, &tsrOpts)) != mDNSNULL) tsrOptsCount++;
                         newptr = PutRR_OS(responseptr, &m->omsg.h.numAdditionals, &nsec.resrec);
+                        if (newTSROpt)
+                        {
+                            if (newptr) TSRDataRecPtrHeadAddTSROpt(&tsrOpts, newTSROpt, rr->resrec.name, m->omsg.h.numAnswers + m->omsg.h.numAdditionals - 1);
+                            else        tsrOptsCount--;
+                        }
                         if (newptr) responseptr = newptr;
                     }
                 }
@@ -3241,42 +3562,58 @@
         if (m->omsg.h.numAnswers || m->omsg.h.numAdditionals)
         {
             // If we have data to send, add OWNER/TRACER/OWNER+TRACER option if necessary, then send packet
-            if (OwnerRecordSpace || TraceRecordSpace)
+            if (OwnerRecordSpace || TraceRecordSpace || !SLIST_EMPTY(&tsrOpts))
             {
+                mDNSu8 *saveptr;
                 AuthRecord opt;
                 mDNS_SetupResourceRecord(&opt, mDNSNULL, mDNSInterface_Any, kDNSType_OPT, kStandardTTL, kDNSRecordTypeKnownUnique, AuthRecordAny, mDNSNULL, mDNSNULL);
                 opt.resrec.rrclass    = NormalMaxDNSMessageData;
-                opt.resrec.rdlength   = sizeof(rdataOPT);
-                opt.resrec.rdestimate = sizeof(rdataOPT);
-                if (OwnerRecordSpace && TraceRecordSpace)
+                opt.resrec.rdlength   = 0;
+                opt.resrec.rdestimate = 0;
+                mDNSu16 optCount      = 0;
+                if (OwnerRecordSpace)
                 {
-                    opt.resrec.rdlength   += sizeof(rdataOPT); // Two options in this OPT record
+                    opt.resrec.rdlength   += sizeof(rdataOPT);
                     opt.resrec.rdestimate += sizeof(rdataOPT);
-                    SetupOwnerOpt(m, intf, &opt.resrec.rdata->u.opt[0]);
-                    SetupTracerOpt(m, &opt.resrec.rdata->u.opt[1]);
+                    SetupOwnerOpt(m, intf, &opt.resrec.rdata->u.opt[optCount++]);
                 }
-                else if (OwnerRecordSpace)
+                if (TraceRecordSpace)
                 {
-                    SetupOwnerOpt(m, intf, &opt.resrec.rdata->u.opt[0]);
+                    opt.resrec.rdlength   += sizeof(rdataOPT);
+                    opt.resrec.rdestimate += sizeof(rdataOPT);
+                    SetupTracerOpt(m, &opt.resrec.rdata->u.opt[optCount++]);
                 }
-                else if (TraceRecordSpace)
+                if (!SLIST_EMPTY(&tsrOpts))
                 {
-                    SetupTracerOpt(m, &opt.resrec.rdata->u.opt[0]);
+                    opt.resrec.rdlength   += sizeof(rdataOPT);
+                    opt.resrec.rdestimate += sizeof(rdataOPT);
+                    SetupTSROpt(SLIST_FIRST(&tsrOpts)->tsr, &opt.resrec.rdata->u.opt[optCount++]);
+                    TSRDataRecPtrHeadRemoveAndFreeFirst(&tsrOpts);
                 }
+                // Put record after first TSR
+                saveptr = responseptr;
                 newptr = PutResourceRecord(&m->omsg, responseptr, &m->omsg.h.numAdditionals, &opt.resrec);
+                if (newptr && !SLIST_EMPTY(&tsrOpts))
+                {
+                    mDNSu8 *rdlengthptr = saveptr + 2 + 2 + 4 + 1; // rrtype, rrclass, ttl, 0-length name
+                    newptr = AddTSRROptsToMessage(&tsrOpts, &m->omsg, rdlengthptr, newptr,
+                        m->omsg.data + AbsoluteMaxDNSMessageData);
+                }
                 if (newptr)
                 {
                     responseptr = newptr;
                 }
                 else if (m->omsg.h.numAnswers + m->omsg.h.numAuthorities + m->omsg.h.numAdditionals == 1)
                 {
-                    LogInfo("SendResponses: No space in packet for %s %s OPT record (%d/%d/%d/%d) %s", OwnerRecordSpace ? "OWNER" : "", TraceRecordSpace ? "TRACER" : "",
-                            m->omsg.h.numQuestions, m->omsg.h.numAnswers, m->omsg.h.numAuthorities, m->omsg.h.numAdditionals, ARDisplayString(m, &opt));
+                    LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_ERROR, "SendResponses: No space in packet for %s %s TSR(%d) OPT record (%d/%d/%d/%d) %s",
+                        OwnerRecordSpace ? "OWNER" : "", TraceRecordSpace ? "TRACER" : "", tsrOptsCount,
+                        m->omsg.h.numQuestions, m->omsg.h.numAnswers, m->omsg.h.numAuthorities, m->omsg.h.numAdditionals, ARDisplayString(m, &opt));
                 }
                 else
                 {
-                    LogMsg("SendResponses: How did we fail to have space for %s %s OPT record (%d/%d/%d/%d) %s", OwnerRecordSpace ? "OWNER" : "", TraceRecordSpace ? "TRACER" : "",
-                           m->omsg.h.numQuestions, m->omsg.h.numAnswers, m->omsg.h.numAuthorities, m->omsg.h.numAdditionals, ARDisplayString(m, &opt));
+                    LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_ERROR, "SendResponses: How did we fail to have space for %s %s TSR(%d) OPT record (%d/%d/%d/%d) %s",
+                        OwnerRecordSpace ? "OWNER" : "", TraceRecordSpace ? "TRACER" : "", tsrOptsCount,
+                        m->omsg.h.numQuestions, m->omsg.h.numAnswers, m->omsg.h.numAuthorities, m->omsg.h.numAdditionals, ARDisplayString(m, &opt));
                 }
             }
 
@@ -3302,6 +3639,7 @@
             intf = next;
             pktcount = 0;       // When we move to a new interface, reset packet count back to zero -- NSEC generation logic uses it
         }
+        TSRDataRecPtrHeadFreeList(&tsrOpts);
     }
 
     // ***
@@ -3550,8 +3888,14 @@
     FORALL_CACHERECORDS(slot, cg, cr)
     {
         const domainname *crtarget;
+    #if MDNSRESPONDER_SUPPORTS(COMMON, DNS_PUSH) || MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
+        if (cr->DNSPushSubscribed)                 continue; // Skip records that are subscribed with a push server. [1]
+    #endif
         if (cr->resrec.InterfaceID != InterfaceID) continue; // Skip non-mDNS records and mDNS records from other interfaces.
         if (cr->resrec.rdatahash != namehash)      continue; // Skip records whose rdata hash doesn't match the name hash.
+        // Notes:
+        // 1. If the records are resolved through DNS push subscription, only the push server can ask us to add or
+        //    remove the record, so we do not need to reconfirm it here.
         crtarget = GetRRDomainNameTarget(&cr->resrec);
         if (crtarget && SameDomainName(crtarget, name))
         {
@@ -3583,14 +3927,35 @@
     return(cr);
 }
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, UNICAST_ASSIST)
+mDNSlocal mDNSBool CacheGroupHasAddressOnInterface(const CacheGroup *const cg, mDNSu16 rrtype, const mDNSAddr *const addr, const mDNSInterfaceID interfaceID)
+{
+    mDNS *const m = &mDNSStorage;
+    mDNSBool result = mDNSfalse;
+    const CacheRecord *cr;
+    for (cr = cg ? cg->members : mDNSNULL; cr; cr=cr->next)
+    {
+        if (cr->resrec.rrtype == rrtype                                     &&
+            cr->resrec.InterfaceID == interfaceID                           &&
+            RRExpireTime(cr) - m->timenow > UNICAST_ASSIST_MIN_REFRESH_TIME &&
+            mDNSSameAddress(&cr->sourceAddress, addr))
+        {
+            result = mDNStrue;
+            break;
+        }
+    }
+    return(result);
+}
+#endif
 
+#if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
 mDNSlocal const CacheRecord *FindSPSInCache1(mDNS *const m, const DNSQuestion *const q, const CacheRecord *const c0, const CacheRecord *const c1)
 {
 #ifndef SPC_DISABLED
     CacheGroup *const cg = CacheGroupForName(m, q->qnamehash, &q->qname);
     const CacheRecord *cr, *bestcr = mDNSNULL;
     mDNSu32 bestmetric = 1000000;
-    for (cr = cg ? cg->members : mDNSNULL; cr; cr=cr->next)
+    for (cr = cg ? cg->members : mDNSNULL; cr; cr = cr->next)
         if (cr->resrec.rrtype == kDNSType_PTR && cr->resrec.rdlength >= 6)                      // If record is PTR type, with long enough name,
             if (cr != c0 && cr != c1)                                                           // that's not one we've seen before,
                 if (SameNameCacheRecordAnswersQuestion(cr, q))                                  // and answers our browse query,
@@ -3645,6 +4010,7 @@
     // only if they have equally good metric and support features.
     ReorderSPSByFeature(sps);
 }
+#endif // MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
 
 // Only DupSuppressInfos newer than the specified 'time' are allowed to remain active
 mDNSlocal void ExpireDupSuppressInfo(DupSuppressState *const state, const mDNSs32 time)
@@ -3849,28 +4215,51 @@
 
 // Return true if we should add record rr in the probe packet's authoritative section when probing for ar
 // Otherwise return false.
-mDNSlocal mDNSBool AddRecordInProbe(const AuthRecord *const ar, const mDNSBool hasTSR, const AuthRecord *const rr, const mDNSInterfaceID InterfaceID)
+mDNSlocal mDNSBool AddRecordInProbe(mDNS *const m, const AuthRecord *const ar, const AuthRecord *const rr, 
+    const mDNSInterfaceID InterfaceID)
 {
     // Already marked
     if (rr->IncludeInProbe)
     {
         return mDNSfalse;
     }
+    mDNSBool hasTSR = (mDNSGetTSRForAuthRecord(m, ar) != mDNSNULL);
+
     // Only include TXT record in probe query's authority section if TSR record exist for the name
     if (rr->DependentOn && !hasTSR)
     {
         return mDNSfalse;
     }
+
     if (!IsInterfaceValidForAuthRecord(rr, InterfaceID))
     {
         return mDNSfalse;
     }
-    // rr is not in probing stage and not dependent on other records
-    // This is to exclude record that's already verified, but include TXT and TSR record if it's service registration
-    // Refer to rdar://109086182 and rdar://109635078
-    if (rr->resrec.RecordType != kDNSRecordTypeUnique && !rr->DependentOn)
+
+    // If a probe question is being sent for an AuthRecord and the AuthRecord is associated with a TSR record, then
+    // all of the AuthRecords with the same name from the same client connection need to be present in the
+    // authority section. So if one of them happens to have already gotten past the probing stage, it still needs
+    // to be included. Currently, individually-registered AuthRecords from the same client connection will have the
+    // same non-zero RRSet value.
+    mDNSBool skipProbingStageCheck = mDNSfalse;
+    if (hasTSR)
     {
-        return mDNSfalse;
+        const uintptr_t s1 = ar->RRSet ? ar->RRSet : (uintptr_t)ar;
+        const uintptr_t s2 = rr->RRSet ? rr->RRSet : (uintptr_t)rr;
+        if (s1 == s2)
+        {
+            skipProbingStageCheck = mDNStrue;
+        }
+    }
+    if (!skipProbingStageCheck)
+    {
+        // rr is not in probing stage and not dependent on other records
+        // This is to exclude record that's already verified, but include TXT and TSR record if it's service registration
+        // Refer to rdar://109086182 and rdar://109635078
+        if (rr->resrec.RecordType != kDNSRecordTypeUnique && !rr->DependentOn)
+        {
+            return mDNSfalse;
+        }
     }
     // Has the same name, class, interface with ar
     if (SameResourceRecordNameClassInterface(ar, rr))
@@ -3915,7 +4304,11 @@
         {
             if (m->timenow + TicksTTL(cr)/50 - cr->NextRequiredQuery >= 0)
             {
+#if MDNSRESPONDER_SUPPORTS(APPLE, UNICAST_ASSIST)
                 debugf("Sending %d%% cache expiration query for %s", (!cr->unicastAssistSent ? 75 : 80) + 5 * cr->UnansweredQueries, CRDisplayString(m, cr));
+#else
+                debugf("Sending %d%% cache expiration query for %s", 80 + 5 * cr->UnansweredQueries, CRDisplayString(m, cr));
+#endif
                 q = cr->CRActiveQuestion;
                 ExpireDupSuppressInfoOnInterface(q->DupSuppress, m->timenow - TicksTTL(cr)/20, cr->resrec.InterfaceID);
                 // For uDNS queries (TargetQID non-zero) we adjust LastQTime,
@@ -3942,11 +4335,15 @@
                 if (mDNSOpaque16IsZero(q->TargetQID))
                 {
 #if MDNSRESPONDER_SUPPORTS(APPLE, UNICAST_ASSIST)
-                    if (cr->UnansweredQueries == 0                      &&
-                        !cr->unicastAssistSent                          &&
+                    if (!cr->unicastAssistSent                          &&
                         mDNSAddressIsValidNonZero(&cr->sourceAddress)   &&
                         !mDNSAddrIsDNSMulticast(&cr->sourceAddress))
                     {
+                        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+                            "[Q%u] Sending unicast assist query (expiring) - " PRI_IP_ADDR " " PRI_DM_NAME " %s qhash %x" ,
+                            mDNSVal16(q->TargetQID), &cr->sourceAddress, DM_NAME_PARAM(&q->qname),
+                            DNSTypeName(q->qtype), q->qnamehash);
+
                         InitializeDNSMessage(&m->omsg.h, q->TargetQID, QueryFlags);
                         const mDNSu8 *const limit = m->omsg.data + sizeof(m->omsg.data);
                         const mDNSu16 qclass = q->qclass | kDNSQClass_UnicastResponse;
@@ -4009,14 +4406,62 @@
                 else
 #endif
                 {
-                    //LogInfo("Time to send %##s (%s) %d", q->qname.c, DNSTypeName(q->qtype), m->timenow - NextQSendTime(q));
-                    q->SendQNow = mDNSInterfaceMark;        // Mark this question for sending on all interfaces
-                    if (maxExistingQuestionInterval < q->ThisQInterval)
-                        maxExistingQuestionInterval = q->ThisQInterval;
-                }
-#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
-                Querier_HandleMDNSQuestion(q);
+                    mDNSBool delayQuestion = mDNSfalse;
+#if MDNSRESPONDER_SUPPORTS(APPLE, UNICAST_ASSIST)
+                    if (!q->initialAssistPerformed)
+                    {
+                        __block CacheGroup *const current_cg = CacheGroupForName(m, q->qnamehash, &q->qname);
+                        __block bool assistSentForUnique = false;
+                        q->initialAssistPerformed = mDNStrue;
+                        unicast_assist_addr_enumerate(q->qnamehash, q->InterfaceID,
+                            ^bool(const mDNSAddr * const addr, mDNSInterfaceID ifid, bool unique)
+                        {
+                            bool result = false;
+                            if (!CacheGroupHasAddressOnInterface(current_cg, q->qtype, addr, ifid))
+                            {
+                                LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO,
+                                    "[Q%u] Sending unicast assist query - " PRI_IP_ADDR " %d " PRI_DM_NAME " "
+                                    PUB_DNS_TYPE " qhash %x", mDNSVal16(q->TargetQID), addr,
+                                    IIDPrintable(ifid), DM_NAME_PARAM(&q->qname), DNS_TYPE_PARAM(q->qtype),
+                                    q->qnamehash);
+
+                                InitializeDNSMessage(&m->omsg.h, q->TargetQID, QueryFlags);
+                                const mDNSu8 *const limit = m->omsg.data + sizeof(m->omsg.data);
+                                const mDNSu16 qclass = q->qclass | kDNSQClass_UnicastResponse;
+                                mDNSu8 *const end = putQuestion(&m->omsg, m->omsg.data, limit, &q->qname, q->qtype, qclass);
+                                mDNSSendDNSMessage(m, &m->omsg, end, ifid, mDNSNULL, mDNSNULL, addr,
+                                    MulticastDNSPort, mDNSNULL, q->UseBackgroundTraffic);
+                                q->LastQTime         = m->timenow;
+                                q->LastQTxTime       = m->timenow;
+                                q->RecentAnswerPkts  = 0;
+                                q->SendQNow          = mDNSNULL;
+                                q->ExpectUnicastResp = NonZeroTime(m->timenow);
+                                assistSentForUnique = unique;
+                                result = true;
+                            }
+                            else
+                            {
+                                LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_INFO, "SKIPPED unicast assist query - "
+                                    PRI_IP_ADDR " %d " PRI_DM_NAME " " PUB_S " qhash %x",
+                                    addr, IIDPrintable(ifid), DM_NAME_PARAM(&q->qname),
+                                    DNSTypeName(q->qtype), q->qnamehash);
+                            }
+                            return result;
+                        });
+                        if (assistSentForUnique)
+                        {
+                            delayQuestion = mDNStrue;
+                        }
+                    }
 #endif
+                    if (!delayQuestion)
+                    {
+                        //LogInfo("Time to send %##s (%s) %d", q->qname.c, DNSTypeName(q->qtype), m->timenow - NextQSendTime(q));
+                        q->SendQNow = mDNSInterfaceMark;        // Mark this question for sending on all interfaces
+                        if (maxExistingQuestionInterval < q->ThisQInterval)
+                            maxExistingQuestionInterval = q->ThisQInterval;
+                    }
+                }
             }
         }
         // If m->CurrentQuestion wasn't modified out from under us, advance it now
@@ -4135,7 +4580,7 @@
     {
         ar = m->CurrentRecord;
         m->CurrentRecord = ar->next;
-        if (!AuthRecord_uDNS(ar) && ar->resrec.RecordType == kDNSRecordTypeUnique && ar->resrec.rrtype != kDNSType_TSR)  // For all records that are still probing...
+        if (!AuthRecord_uDNS(ar) && ar->resrec.RecordType == kDNSRecordTypeUnique && ar->resrec.rrtype != kDNSType_OPT)  // For all records that are still probing...
         {
             // 1. If it's not reached its probe time, just make sure we update m->NextScheduledProbe correctly
             if (m->timenow - (ar->LastAPTime + ar->ThisAPInterval) < 0)
@@ -4213,14 +4658,21 @@
             AcknowledgeRecord(m, ar);
     }
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, DISCOVERY_PROXY_CLIENT)
+    mDNSBool queryHasDPCBrowse = mDNSfalse;
+#endif
     // 3. Now we know which queries and probes we're sending,
     // go through our interface list sending the appropriate queries on each interface
     while (intf)
     {
-        int OwnerRecordSpace = (m->AnnounceOwner && intf->MAC.l[0]) ? DNSOpt_Header_Space + DNSOpt_Owner_Space(&m->PrimaryMAC, &intf->MAC) : 0;
+        const mDNSu32 OwnerRecordSpace = DetermineOwnerRecordSpace(intf);
         int TraceRecordSpace = (mDNS_McastTracingEnabled && MDNS_TRACER) ? DNSOpt_Header_Space + DNSOpt_TraceData_Space : 0;
         mDNSu8 *queryptr = m->omsg.data;
         mDNSBool useBackgroundTrafficClass = mDNSfalse;    // set if we should use background traffic class
+        TSRDataPtrRecHead tsrOpts = SLIST_HEAD_INITIALIZER(tsrOpts);
+        TSROptData *newTSROpt;
+        mDNSu16 tsrOptsCount = 0;
+        mDNSu32 tsrHeaderSpace = (OwnerRecordSpace || TraceRecordSpace) ? 0 : DNSOpt_Header_Space;
 
         InitializeDNSMessage(&m->omsg.h, zeroID, QueryFlags);
         if (KnownAnswerList) verbosedebugf("SendQueries:   KnownAnswerList set... Will continue from previous packet");
@@ -4241,11 +4693,18 @@
                            SuppressOnThisInterface(q->DupSuppress, intf) ? "Suppressing" : "Putting    ",
                            q->qname.c, DNSTypeName(q->qtype), queryptr - m->omsg.data, queryptr + answerforecast - m->omsg.data);
 
+                    mDNSBool updateInterface = mDNSfalse;
                     // If interface is P2P type, verify that query should be sent over it.
                     if (!mDNSPlatformValidQuestionForInterface(q, intf))
                     {
-                        q->SendQNow = (q->InterfaceID || !q->SendOnAll) ? mDNSNULL : GetNextActiveInterfaceID(intf);
+                        updateInterface = mDNStrue;
                     }
+                #if MDNSRESPONDER_SUPPORTS(APPLE, DISCOVERY_PROXY_CLIENT)
+                    else if (DPCSuppressMDNSQuery(q, intf->InterfaceID))
+                    {
+                        updateInterface = mDNStrue;
+                    }
+                #endif
                     // If we're suppressing this question, or we successfully put it, update its SendQNow state
                     else if ((Suppress = SuppressOnThisInterface(q->DupSuppress, intf)) ||
                         BuildQuestion(m, intf, &m->omsg, &queryptr, q, &kalistptr, &answerforecast))
@@ -4278,8 +4737,7 @@
                             q->metrics.querySendCount++;
                         }
                     #endif
-
-                        q->SendQNow = (q->InterfaceID || !q->SendOnAll) ? mDNSNULL : GetNextActiveInterfaceID(intf);
+                        updateInterface = mDNStrue;
                         if (q->WakeOnResolveCount)
                         {
                             mDNSSendWakeOnResolve(m, q);
@@ -4292,13 +4750,50 @@
                             useBackgroundTrafficClass = mDNStrue;
                         }
                     }
+                    if (updateInterface)
+                    {
+                        q->SendQNow = (q->InterfaceID || !q->SendOnAll) ? mDNSNULL : GetNextActiveInterfaceID(intf);
+                    }
                 }
             }
-
+        #if MDNSRESPONDER_SUPPORTS(APPLE, DISCOVERY_PROXY_CLIENT)
+            // If the message being constructed for the current interface contains at least one non-probe question,
+            // try to opportunistically include the Discovery Proxy browse's question as well.
+            if (DPCFeatureEnabled() && !queryHasDPCBrowse && (m->omsg.h.numQuestions > 0))
+            {
+                const mDNSu8 *questionPtr = m->omsg.data;
+                const mDNSu8 *const end = queryptr;
+                for (mDNSu32 i = 0; i < m->omsg.h.numQuestions; i++)
+                {
+                    DNSQuestion question;
+                    questionPtr = getQuestion(&m->omsg, questionPtr, end, mDNSInterface_Any, &question);
+                    if (!questionPtr)
+                    {
+                        break;
+                    }
+                    question.qclass &= ~kDNSQClass_UnicastResponse;
+                    if ((question.qtype == DPCBrowse.qtype) && (question.qclass == DPCBrowse.qclass) &&
+                        (question.qnamehash == DPCBrowse.qnamehash) && SameDomainName(&question.qname, &DPCBrowse.qname))
+                    {
+                        queryHasDPCBrowse = mDNStrue;
+                        break;
+                    }
+                }
+                if (!queryHasDPCBrowse)
+                {
+                    DPCBrowse.SendQNow = intf->InterfaceID;
+                    DPCBrowse.RequestUnicast = kDefaultRequestUnicastCount;
+                    BuildQuestion(m, intf, &m->omsg, &queryptr, &DPCBrowse, &kalistptr, &answerforecast);
+                    DPCBrowse.SendQNow = mDNSNULL;
+                    queryHasDPCBrowse = mDNStrue;
+                }
+            }
+        #endif
             // Put probe questions in this packet
             for (ar = m->ResourceRecords; ar; ar=ar->next)
             {   // Skip if already marked for probing, or interface does not match, or TSR record
-                if (ar->IncludeInProbe || (ar->SendRNow != intf->InterfaceID) || (ar->resrec.rrtype == kDNSType_TSR))
+                if (ar->IncludeInProbe || (ar->SendRNow != intf->InterfaceID) || 
+                    (ar->resrec.rrtype == kDNSType_OPT))
                     continue;
 
                 // If interface is a P2P variant, verify that the probe should be sent over it.
@@ -4332,10 +4827,15 @@
                         }
                     }
 
-                    const mDNSBool hasTSR = (mDNSGetTSRRecord(m, ar) != mDNSNULL);
+                    if (TSROptGetIfNew(m, ar, &tsrOpts) != mDNSNULL)
+                    {
+                        forecast += (DNSOpt_TSRData_Space + ((tsrOptsCount == 0) ? tsrHeaderSpace : 0));
+                        tsrOptsCount++;
+                    }
+
                     for (const AuthRecord *tmp = m->ResourceRecords; tmp; tmp = tmp->next)
                     {
-                        if (AddRecordInProbe(ar, hasTSR, tmp, intf->InterfaceID))
+                        if (AddRecordInProbe(m, ar, tmp, intf->InterfaceID))
                         {
                             // compressed name (2) type (2) class (2) TTL (4) rdlength (2) estimated rdata length
                             forecast = forecast + 12 + tmp->resrec.rdestimate;
@@ -4369,7 +4869,7 @@
                     answerforecast = forecast;
                     for (AuthRecord *tmp = m->ResourceRecords; tmp; tmp = tmp->next)
                     {
-                        if (AddRecordInProbe(ar, hasTSR, tmp, intf->InterfaceID))
+                        if (AddRecordInProbe(m, ar, tmp, intf->InterfaceID))
                         {
                             tmp->SendRNow = (ar->resrec.InterfaceID) ? mDNSNULL : GetNextActiveInterfaceID(intf);
                             tmp->IncludeInProbe = mDNStrue;
@@ -4390,7 +4890,7 @@
             CacheRecord *ka = KnownAnswerList;
             mDNSu32 SecsSinceRcvd = ((mDNSu32)(m->timenow - ka->TimeRcvd)) / mDNSPlatformOneSecond;
             mDNSu8 *newptr = PutResourceRecordTTLWithLimit(&m->omsg, queryptr, &m->omsg.h.numAnswers, &ka->resrec, ka->resrec.rroriginalttl - SecsSinceRcvd,
-                                                           m->omsg.data + NormalMaxDNSMessageData - OwnerRecordSpace - TraceRecordSpace);
+                                                           m->omsg.data + NormalMaxDNSMessageData - RR_OPT_SPACE);
             if (newptr)
             {
                 verbosedebugf("SendQueries:   Put %##s (%s) at %d - %d",
@@ -4410,6 +4910,7 @@
             }
         }
 
+        tsrOptsCount = 0;
         for (ar = m->ResourceRecords; ar; ar=ar->next)
         {
             if (ar->IncludeInProbe)
@@ -4419,47 +4920,69 @@
                 ar->IncludeInProbe = mDNSfalse;
                 if (newptr) queryptr = newptr;
                 else LogMsg("SendQueries:   How did we fail to have space for the Update record %s", ARDisplayString(m,ar));
+
+                if ((newTSROpt = TSROptGetIfNew(m, ar, &tsrOpts)) != mDNSNULL)
+                {
+                    tsrOptsCount++;
+                    TSRDataRecPtrHeadAddTSROpt(&tsrOpts, newTSROpt, ar->resrec.name, m->omsg.h.numAnswers + m->omsg.h.numAdditionals + m->omsg.h.numAuthorities - 1);
+                }
             }
         }
 
         if (queryptr > m->omsg.data)
         {
             // If we have data to send, add OWNER/TRACER/OWNER+TRACER option if necessary, then send packet
-            if (OwnerRecordSpace || TraceRecordSpace)
+            if (OwnerRecordSpace || TraceRecordSpace || !SLIST_EMPTY(&tsrOpts))
             {
+                mDNSu8 *saveptr;
                 AuthRecord opt;
                 mDNS_SetupResourceRecord(&opt, mDNSNULL, mDNSInterface_Any, kDNSType_OPT, kStandardTTL, kDNSRecordTypeKnownUnique, AuthRecordAny, mDNSNULL, mDNSNULL);
                 opt.resrec.rrclass    = NormalMaxDNSMessageData;
-                opt.resrec.rdlength   = sizeof(rdataOPT);
-                opt.resrec.rdestimate = sizeof(rdataOPT);
-                if (OwnerRecordSpace && TraceRecordSpace)
+                opt.resrec.rdlength   = 0;
+                opt.resrec.rdestimate = 0;
+                mDNSu16 optCount      = 0;
+                if (OwnerRecordSpace)
                 {
-                    opt.resrec.rdlength   += sizeof(rdataOPT);  // Two options in this OPT record
+                    opt.resrec.rdlength   += sizeof(rdataOPT);
                     opt.resrec.rdestimate += sizeof(rdataOPT);
-                    SetupOwnerOpt(m, intf, &opt.resrec.rdata->u.opt[0]);
-                    SetupTracerOpt(m, &opt.resrec.rdata->u.opt[1]);
+                    SetupOwnerOpt(m, intf, &opt.resrec.rdata->u.opt[optCount++]);
                 }
-                else if (OwnerRecordSpace)
+                if (TraceRecordSpace)
                 {
-                    SetupOwnerOpt(m, intf, &opt.resrec.rdata->u.opt[0]);
+                    opt.resrec.rdlength   += sizeof(rdataOPT);
+                    opt.resrec.rdestimate += sizeof(rdataOPT);
+                    SetupTracerOpt(m, &opt.resrec.rdata->u.opt[optCount++]);
                 }
-                else if (TraceRecordSpace)
+                if (!SLIST_EMPTY(&tsrOpts))
                 {
-                    SetupTracerOpt(m, &opt.resrec.rdata->u.opt[0]);
+                    opt.resrec.rdlength   += sizeof(rdataOPT);
+                    opt.resrec.rdestimate += sizeof(rdataOPT);
+                    SetupTSROpt(SLIST_FIRST(&tsrOpts)->tsr, &opt.resrec.rdata->u.opt[optCount++]);
+                    TSRDataRecPtrHeadRemoveAndFreeFirst(&tsrOpts);
                 }
-                queryptr = PutResourceRecordTTLWithLimit(&m->omsg, queryptr, &m->omsg.h.numAdditionals,
-                                                         &opt.resrec, opt.resrec.rroriginalttl, m->omsg.data + AbsoluteMaxDNSMessageData);
+                // Put record after first TSR
+                saveptr = queryptr;
+                queryptr = PutResourceRecordTTLWithLimit(&m->omsg, queryptr, &m->omsg.h.numAdditionals, &opt.resrec,
+                    opt.resrec.rroriginalttl, m->omsg.data + AbsoluteMaxDNSMessageData);
+                if (queryptr && !SLIST_EMPTY(&tsrOpts))
+                {
+                    mDNSu8 *rdlengthptr = saveptr + 2 + 2 + 4 + 1; // rrtype, rrclass, ttl, 0-length name
+                    queryptr = AddTSRROptsToMessage(&tsrOpts, &m->omsg, rdlengthptr, queryptr,
+                        m->omsg.data + AbsoluteMaxDNSMessageData);
+                }
                 if (!queryptr)
                 {
-                    LogMsg("SendQueries: How did we fail to have space for %s %s OPT record (%d/%d/%d/%d) %s", OwnerRecordSpace ? "OWNER" : "", TraceRecordSpace ? "TRACER" : "",
-                           m->omsg.h.numQuestions, m->omsg.h.numAnswers, m->omsg.h.numAuthorities, m->omsg.h.numAdditionals, ARDisplayString(m, &opt));
+                    LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_ERROR, "SendQueries: How did we fail to have space for %s %s TSR(%d) OPT record (%d/%d/%d/%d) %s",
+                        OwnerRecordSpace ? "OWNER" : "", TraceRecordSpace ? "TRACER" : "", tsrOptsCount,
+                        m->omsg.h.numQuestions, m->omsg.h.numAnswers, m->omsg.h.numAuthorities, m->omsg.h.numAdditionals, ARDisplayString(m, &opt));
                 }
                 if (queryptr > m->omsg.data + NormalMaxDNSMessageData)
                 {
                     if (m->omsg.h.numQuestions != 1 || m->omsg.h.numAnswers != 0 || m->omsg.h.numAuthorities != 1 || m->omsg.h.numAdditionals != 1)
-                        LogMsg("SendQueries: Why did we generate oversized packet with %s %s OPT record %p %p %p (%d/%d/%d/%d) %s", OwnerRecordSpace ? "OWNER" : "",
-                                TraceRecordSpace ? "TRACER" : "", m->omsg.data, m->omsg.data + NormalMaxDNSMessageData, queryptr, m->omsg.h.numQuestions, m->omsg.h.numAnswers,
-                                m->omsg.h.numAuthorities, m->omsg.h.numAdditionals, ARDisplayString(m, &opt));
+                        LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_ERROR, "SendQueries: Why did we generate oversized packet with %s %s TSR(%d) OPT record %p %p %p (%d/%d/%d/%d) %s",
+                            OwnerRecordSpace ? "OWNER" : "", TraceRecordSpace ? "TRACER" : "", tsrOptsCount,
+                            m->omsg.data, m->omsg.data + NormalMaxDNSMessageData, queryptr, m->omsg.h.numQuestions,
+                            m->omsg.h.numAnswers, m->omsg.h.numAuthorities, m->omsg.h.numAdditionals, ARDisplayString(m, &opt));
                 }
             }
 
@@ -4468,6 +4991,7 @@
             debugf("SendQueries:   Sending %d Question%s %d Answer%s %d Update%s on %d (%s)",
                    m->omsg.h.numQuestions,   m->omsg.h.numQuestions   == 1 ? "" : "s",
                    m->omsg.h.numAnswers,     m->omsg.h.numAnswers     == 1 ? "" : "s",
+                   m->omsg.h.numAdditionals, m->omsg.h.numAdditionals == 1 ? "" : "s",
                    m->omsg.h.numAuthorities, m->omsg.h.numAuthorities == 1 ? "" : "s", IIDPrintable(intf->InterfaceID), intf->ifname);
             if (intf->IPv4Available) mDNSSendDNSMessage(m, &m->omsg, queryptr, intf->InterfaceID, mDNSNULL, mDNSNULL, &AllDNSLinkGroup_v4, MulticastDNSPort, mDNSNULL, useBackgroundTrafficClass);
             if (intf->IPv6Available) mDNSSendDNSMessage(m, &m->omsg, queryptr, intf->InterfaceID, mDNSNULL, mDNSNULL, &AllDNSLinkGroup_v6, MulticastDNSPort, mDNSNULL, useBackgroundTrafficClass);
@@ -4481,7 +5005,11 @@
         {
             const NetworkInterfaceInfo *next = GetFirstActiveInterface(intf->next);
             intf = next;
+        #if MDNSRESPONDER_SUPPORTS(APPLE, DISCOVERY_PROXY_CLIENT)
+            queryHasDPCBrowse = mDNSfalse;
+        #endif
         }
+        TSRDataRecPtrHeadFreeList(&tsrOpts);
     }
 
     // 4. Final housekeeping
@@ -4490,7 +5018,7 @@
     for (ar = m->ResourceRecords; ar; ar=ar->next)
         if (ar->SendRNow)
         {
-            if (ar->ARType != AuthRecordLocalOnly && ar->ARType != AuthRecordP2P && ar->resrec.rrtype != kDNSType_TSR)
+            if (ar->ARType != AuthRecordLocalOnly && ar->ARType != AuthRecordP2P)
                 LogInfo("SendQueries: No active interface %d to send probe: %d %s",
                         IIDPrintable(ar->SendRNow), IIDPrintable(ar->resrec.InterfaceID), ARDisplayString(m, ar));
             ar->SendRNow = mDNSNULL;
@@ -4539,21 +5067,6 @@
     }
 }
 
-// Get TSR record that has the same name as rr;
-// Return the TSR record if found, otherwise return NULL.
-mDNSexport AuthRecord *mDNSGetTSRRecord(mDNS *const m, const AuthRecord *const rr)
-{
-    AuthRecord *ar = mDNSNULL;
-    for (ar = m->ResourceRecords; ar; ar = ar->next)
-    {
-        if (ar->resrec.rrtype == kDNSType_TSR && SameResourceRecordNameClassInterface(ar, rr))
-        {
-            return ar;
-        }
-    }
-    return ar;
-}
-
 mDNSlocal void SendWakeup(mDNS *const m, mDNSInterfaceID InterfaceID, mDNSEthAddr *EthAddr, mDNSOpaque48 *password, mDNSBool unicastOnly)
 {
     int i, j;
@@ -4731,6 +5244,7 @@
         switch (q->ExpRecordPolicy)
         {
             case mDNSExpiredRecordPolicy_DoNotUse:
+            MDNS_COVERED_SWITCH_DEFAULT:
                 break;
 
             case mDNSExpiredRecordPolicy_UseCached:
@@ -4888,14 +5402,7 @@
             case QC_suppressed:
                 // A negative response is forced to be delivered to the callback;
                 // Fall through.
-    #ifdef __clang__
-        #pragma clang diagnostic push
-        #pragma clang diagnostic ignored "-Wcovered-switch-default"
-    #endif
-            default:
-    #ifdef __clang__
-        #pragma clang diagnostic pop
-    #endif
+            MDNS_COVERED_SWITCH_DEFAULT:
                 break;
         }
     }
@@ -5759,6 +6266,20 @@
             q->LastQTime += m->RandomQueryDelay;
         }
     }
+#if MDNSRESPONDER_SUPPORTS(APPLE, DISCOVERY_PROXY_CLIENT)
+    DPCHandleNewQuestion(q);
+#endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
+    if (ActiveQuestion(q) && dns_question_uses_dns_polling(q))
+    {
+        q->LongLived = mDNStrue;
+        q->state = LLQ_Poll;
+        q->ThisQInterval = LLQ_POLL_INTERVAL;
+        // No matter whether the answer is cached or nor, DNS polling always follows the same query schedule.
+        q->LastQTime = m->timenow - q->ThisQInterval + 1;
+    }
+#endif
 
     // IN ALL CASES make sure that m->NextScheduledQuery is set appropriately.
     // In cases where m->NewQuestions->DelayAnswering is set, we may have delayed generating our
@@ -5996,7 +6517,7 @@
     rr->TimeRcvd          = m->timenow - mDNSPlatformOneSecond * 60;
     rr->UnansweredQueries = MaxUnansweredQueries;
     rr->resrec.rroriginalttl     = 0;
-#if MDNSRESPONDER_SUPPORTS(COMMON, DNS_PUSH)
+#if MDNSRESPONDER_SUPPORTS(COMMON, DNS_PUSH) || MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
     rr->DNSPushSubscribed = mDNSfalse;
 #endif
     SetNextCacheCheckTimeForRecord(m, rr);
@@ -6286,11 +6807,13 @@
         ResolverDiscovery_PerformPeriodicTasks();
     #endif
 
+    #if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
         // Clear AnnounceOwner if necessary. (Do this *before* SendQueries() and SendResponses().)
         if (m->AnnounceOwner && m->timenow - m->AnnounceOwner >= 0)
         {
             m->AnnounceOwner = 0;
         }
+    #endif
 
         if (m->DelaySleep && m->timenow - m->DelaySleep >= 0)
         {
@@ -6783,9 +7306,11 @@
                     // that will be offloaded. If not, we should prevent sleep.
                     // This check will be possible once the lower layers provide an API to query the space available for offloads on the NIC.
                     {
+                    #if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
                         // Disallow sleep if there is no sleep proxy server
                         const CacheRecord *cr = FindSPSInCache1(m, &intf->NetWakeBrowse, mDNSNULL, mDNSNULL);
                         if ( cr == mDNSNULL)
+                    #endif
                         {
                             allowSleep = mDNSfalse;
                             mDNS_snprintf(reason, sizeof(reason), "No sleep proxy server on %s", intf->ifname);
@@ -6793,6 +7318,7 @@
                                 " has no sleep proxy server", intf->ifname);
                             break;
                         }
+                    #if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
                         else if (m->SPSType != 0)
                         {
                             mDNSu32 mymetric = LocalSPSMetric(m);
@@ -6806,6 +7332,7 @@
                                 break;
                             }
                         }
+                    #endif
                     }
                 }
             }
@@ -6819,6 +7346,7 @@
 #endif /* !defined(IDLESLEEPCONTROL_DISABLED) */
 }
 
+#if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
 mDNSlocal mDNSBool mDNSUpdateOkToSend(mDNS *const m, AuthRecord *rr, NetworkInterfaceInfo *const intf, mDNSu32 scopeid)
 {
     // If it is not a uDNS record, check to see if the updateid is zero. "updateid" is cleared when we have
@@ -6844,6 +7372,7 @@
 
     return mDNSfalse;
 }
+#endif
 
 mDNSexport void UpdateRMAC(mDNS *const m, void *context)
 {
@@ -6976,6 +7505,7 @@
     return mStatus_NoError;
 }
 
+#if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
 mDNSlocal void SendSPSRegistrationForOwner(mDNS *const m, NetworkInterfaceInfo *const intf, const mDNSOpaque16 id, const OwnerOptData *const owner)
 {
     const int optspace = DNSOpt_Header_Space + DNSOpt_LeaseData_Space + DNSOpt_Owner_Space(&m->PrimaryMAC, &intf->MAC);
@@ -7137,6 +7667,7 @@
         if (mDNSPlatformMemSame(&rr->WakeUp, &ar->WakeUp, sizeof(rr->WakeUp))) return mDNSfalse;
     return mDNStrue;
 }
+#endif // MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
 
 mDNSlocal void mDNSCoreStoreProxyRR(mDNS *const m, const mDNSInterfaceID InterfaceID, AuthRecord *const rr)
 {
@@ -7237,6 +7768,7 @@
     }
 }
 
+#if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
 mDNSlocal void SendSPSRegistration(mDNS *const m, NetworkInterfaceInfo *const intf, const mDNSOpaque16 id)
 {
     AuthRecord *ar;
@@ -7340,6 +7872,7 @@
         mDNS_Unlock(m);
     }
 }
+#endif // MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
 
 mDNSexport mDNSBool mDNSCoreHaveAdvertisedMulticastServices(mDNS *const m)
 {
@@ -7452,7 +7985,9 @@
     mDNSBool SendGoodbyes = mDNStrue;
     mDNSBool WakeOnlyService  = mDNSfalse;
     mDNSBool invokeKACallback = mDNStrue;
+#if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
     const CacheRecord *sps[3] = { mDNSNULL };
+#endif
     mDNSOpaque64 updateIntID = zeroOpaque64;
     mDNSInterfaceID registeredIntfIDS[128] = { 0 };
     mDNSu32 registeredCount = 0;
@@ -7506,6 +8041,7 @@
                 skipFullSleepProxyRegistration = mDNStrue;
             }
 
+        #if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
             if (!skipFullSleepProxyRegistration)
             {
                 FindSPSInCache(m, &intf->NetWakeBrowse, sps);
@@ -7550,6 +8086,7 @@
                     }
                 }
             }
+        #endif // MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
         }
     }
 
@@ -7665,7 +8202,6 @@
         mDNSu32 slot;
         CacheGroup *cg;
         CacheRecord *cr;
-        NetworkInterfaceInfo *intf;
         mDNSs32 currtime, diff;
 
         mDNS_Lock(m);
@@ -7677,12 +8213,14 @@
         {
             m->SleepState = SleepState_Awake;
             m->SleepSeqNum++;
+        #if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
             if (m->SentSleepProxyRegistration)		// Include OWNER option in packets for 60 seconds after waking
             {
                 m->SentSleepProxyRegistration = mDNSfalse;
                 m->AnnounceOwner = NonZeroTime(m->timenow + 60 * mDNSPlatformOneSecond);
                 LogInfo("mDNSCoreMachineSleep: Waking, Setting AnnounceOwner");
             }
+        #endif
             // If the machine wakes and then immediately tries to sleep again (e.g. a maintenance wake)
             // then we enforce a minimum delay of five seconds before we begin sleep processing.
             // This is to allow time for the Ethernet link to come up, DHCP to get an address, mDNS to issue queries, etc.,
@@ -7696,8 +8234,14 @@
             mDNSCoreBeSleepProxyServer_internal(m, m->SPSType, m->SPSPortability, m->SPSMarginalPower, m->SPSTotalPower, m->SPSFeatureFlags);
         }
         m->mDNSStats.Wakes++;
+#if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
         // ... and the same for NextSPSAttempt
-        for (intf = GetFirstActiveInterface(m->HostInterfaces); intf; intf = GetFirstActiveInterface(intf->next)) intf->NextSPSAttempt = -1;
+        NetworkInterfaceInfo *intf;
+        for (intf = GetFirstActiveInterface(m->HostInterfaces); intf; intf = GetFirstActiveInterface(intf->next))
+        {
+            intf->NextSPSAttempt = -1;
+        }
+#endif
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
         Querier_HandleWake();
@@ -7798,7 +8342,6 @@
 {
     DNSQuestion *q;
     AuthRecord *rr;
-    NetworkInterfaceInfo *intf;
 
     mDNS_Lock(m);
 
@@ -7809,6 +8352,8 @@
 
     m->NextScheduledSPRetry = now + 0x40000000UL;
 
+#if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
+    NetworkInterfaceInfo *intf;
     // See if we might need to retransmit any lost Sleep Proxy Registrations
     for (intf = GetFirstActiveInterface(m->HostInterfaces); intf; intf = GetFirstActiveInterface(intf->next))
         if (intf->NextSPSAttempt >= 0)
@@ -7843,6 +8388,7 @@
         if (!AuthRecord_uDNS(rr) && rr->resrec.RecordType > kDNSRecordTypeDeregistering)
             if (!mDNSOpaque64IsZero(&rr->updateIntID))
             { LogSPS("mDNSCoreReadyForSleep: waiting for SPS updateIntID 0x%x 0x%x (updateid %d) %s", rr->updateIntID.l[1], rr->updateIntID.l[0], mDNSVal16(rr->updateid), ARDisplayString(m,rr)); goto spsnotready; }
+#endif
 
     // Scan list of private LLQs, and make sure they've all completed their handshake with the server
     for (q = m->Questions; q; q = q->next)
@@ -7863,6 +8409,7 @@
     mDNS_Unlock(m);
     return mDNStrue;
 
+#if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
 spsnotready:
 
     // If we failed to complete sleep proxy registration within ten seconds, we give up on that
@@ -7897,6 +8444,7 @@
 
         SendSleepGoodbyes(m, mDNStrue, mDNStrue);
     }
+#endif
 
 notready:
     mDNS_Unlock(m);
@@ -8147,79 +8695,59 @@
     return(mDNStrue);
 }
 
-// Compare local TSR value with TSR value in packet.
-// Return +1 if our tsr_value is newer(we win).
-// Otherwise return -1(we lose).
-mDNSlocal int CompareTSRValue(const ResourceRecord *const ourTSR, const ResourceRecord *const pktTSR)
+// If we don't have TSR record or probe doesn't have TSR record that has the same name with auth record, return 0;
+// If both have TSR, then compare tsr_value in our TSR AuthRecord and the TSR record in probe.
+mDNSexport eTSRCheckResult CheckTSRForResourceRecord(const TSROptData *curTSROpt, const ResourceRecord *ourTSRRec)
 {
-    int result = 1;
-    // tsr_value stored locally is absolute time.
-    mDNSs32 ourTimeOfReceipt = ourTSR->rdata->u.tsr_value;
-    // tsr_value in packet is relative time.
-    mDNSs32 pktTimeSinceReceived = pktTSR->rdata->u.tsr_value;
-    mDNSs32 pktTimeOfReceipt;
-
-    // out of range tsr_value in pkt
-    if (pktTimeSinceReceived < 0 || pktTimeSinceReceived > MaxTimeSinceReceived)
+#define TSR_QUANTIZATION_SECS    2
+    eTSRCheckResult result = eTSRCheckNoKeyMatch;
+    if (curTSROpt && ourTSRRec)
     {
-        LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT, "Out of range pktTimeSinceReceived %d in Pkt record", pktTimeSinceReceived);
-        pktTimeSinceReceived = MaxTimeSinceReceived;
+        const TSROptData *ourTSROpt = &ourTSRRec->rdata->u.opt[0].u.tsr;
+        if (ourTSROpt->hostkeyHash == curTSROpt->hostkeyHash)
+        {
+            result = eTSRCheckKeyMatch;
+            // tsr_value stored locally is absolute time.
+            mDNSs32 ourTimeOfReceipt = ourTSROpt->timeStamp;
+            // tsr_value in packet is relative time.
+            mDNSs32 pktTimeSinceReceived = curTSROpt->timeStamp;
+            mDNSs32 pktTimeOfReceipt;
+            // out of range tsr_value in pkt
+            if (pktTimeSinceReceived < 0 || pktTimeSinceReceived > MaxTimeSinceReceived)
+            {
+                LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT, "CheckTSR - Out of range pktTimeSinceReceived %d in Pkt record", pktTimeSinceReceived);
+                pktTimeSinceReceived = MaxTimeSinceReceived;
+            }
+            pktTimeOfReceipt = mDNSPlatformContinuousTimeSeconds() - pktTimeSinceReceived;
+            // tsr in probe is newer counted as we lose.
+            if (abs(ourTimeOfReceipt - pktTimeOfReceipt) > TSR_QUANTIZATION_SECS)
+            {
+                result =  (ourTimeOfReceipt < pktTimeOfReceipt) ? eTSRCheckLose : eTSRCheckWin;
+                LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
+                    "CheckTSR - pktTimeOfReceipt: %d %x " PUB_S " ourTimeOfReceipt: %d %x",
+                    pktTimeOfReceipt, curTSROpt->hostkeyHash, result < 0 ? "lose" : "win", ourTimeOfReceipt,
+                    ourTSROpt->hostkeyHash);
+            }
+        }
     }
-    pktTimeOfReceipt = mDNSPlatformContinuousTimeSeconds() - pktTimeSinceReceived;
-    // tsr in probe is newer, equal counted as we lose.
-    if (ourTimeOfReceipt <= pktTimeOfReceipt)
-    {
-        result = -1;
-    }
-    LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT, "CompareTSRValue: Pkt Record - name: " PRI_DM_NAME
-              ", interface id: %p, pktTimeOfReceipt: %d", DM_NAME_PARAM(pktTSR->name), pktTSR->InterfaceID, pktTimeOfReceipt);
-    LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT, "CompareTSRValue: Our Record - conflict: " PUB_S
-              ", interface id: %p, ourTimeOfReceipt: %d", result < 0 ? "lose" : "win", ourTSR->InterfaceID, ourTimeOfReceipt);
     return result;
 }
 
-// If we don't have TSR record or probe doesn't have TSR record that has the same name with auth record, return 0;
-// If both have TSR, then compare tsr_value in our TSR AuthRecord and the TSR record in probe.
-// Return +1 if both have TSR and we win.
-// Return -1 if both have TSR and we lose.
-mDNSlocal int CheckTSR(mDNS *const m, const DNSMessage *const query, const mDNSu8 *const end,
-                       const DNSQuestion *const q, const AuthRecord *const ar)
+mDNSlocal eTSRCheckResult CheckTSRForAuthRecord(mDNS *const m, const TSROptData *curTSROpt, const AuthRecord *const ar)
 {
-    int i;
-    int result = 0;
-    const mDNSu8 *ptr = LocateAuthorities(query, end);
-    const AuthRecord *ourTSR = mDNSGetTSRRecord(m, ar);
-
-    if (ourTSR == mDNSNULL)
+    const AuthRecord *ourTSR = mDNSGetTSRForAuthRecord(m, ar);
+    if (ourTSR)
     {
-        goto done;
+        return CheckTSRForResourceRecord(curTSROpt, &ourTSR->resrec);
     }
-    for (i = 0; i < query->h.numAuthorities && ptr; i++)
-    {
-        ptr = GetLargeResourceRecord(m, query, ptr, end, q->InterfaceID, kDNSRecordTypePacketAuth, &m->rec);
-        if (!ptr)
-        {
-            break;
-        }
-        // Packet has TSR.
-        if (m->rec.r.resrec.rrtype == kDNSType_TSR && ResourceRecordNameClassInterfaceMatch(&m->rec.r.resrec, &ar->resrec))
-        {
-            result = CompareTSRValue(&ourTSR->resrec, &m->rec.r.resrec);
-            mDNSCoreResetRecord(m);
-            goto done;
-        }
-        mDNSCoreResetRecord(m);
-    }
-
-done:
-    return result;
+    return eTSRCheckNoKeyMatch;
 }
 
 // Note: ResolveSimultaneousProbe calls mDNS_Deregister_internal which can call a user callback, which may change
 // the record list and/or question list.
 // Any code walking either list must use the CurrentQuestion and/or CurrentRecord mechanism to protect against this.
 mDNSlocal void ResolveSimultaneousProbe(mDNS *const m, const DNSMessage *const query, const mDNSu8 *const end,
-                                        DNSQuestion *q, AuthRecord *our)
+    DNSQuestion *q, AuthRecord *our, const TSROptData *curTSR)
 {
     int i;
     const mDNSu8 *ptr = LocateAuthorities(query, end);
@@ -8232,6 +8760,24 @@
         if (m->rec.r.resrec.RecordType != kDNSRecordTypePacketNegative && CacheRecordAnswersQuestion(&m->rec.r, q))
         {
             FoundUpdate = mDNStrue;
+            if (curTSR)
+            {
+                // When conflict happens, look for TSR loss.
+                eTSRCheckResult tsrResult = CheckTSRForAuthRecord(m, curTSR, our);
+                if (tsrResult == eTSRCheckLose)
+                {
+                    LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
+                        "ResolveSimultaneousProbe - deregistering " PRI_DM_NAME " type " PUB_S " on interface id: %p due to TSR conflict",
+                        DM_NAME_PARAM(our->resrec.name), DNSTypeName(our->resrec.rrtype), our->resrec.InterfaceID);
+                    mDNS_Deregister_internal(m, our, mDNS_Dereg_stale);
+                    goto exit;
+                }
+                if (tsrResult != eTSRCheckNoKeyMatch) // No else
+                {
+                    goto exit;
+                }
+            }
+
             if (PacketRRConflict(m, our, &m->rec.r))
             {
                 int result = 0;
@@ -8244,14 +8790,6 @@
                     const char *const msg = (result < 0) ? "lost:" : (result > 0) ? "won: " : "tie: ";
                     LogMsg("ResolveSimultaneousProbe: %p Pkt Record:        %08lX %s", q->InterfaceID, m->rec.r.resrec.rdatahash, CRDisplayString(m, &m->rec.r));
                     LogMsg("ResolveSimultaneousProbe: %p Our Record %d %s %08lX %s", our->resrec.InterfaceID, our->ProbeCount, msg, our->resrec.rdatahash, ARDisplayString(m, our));
-                    // When conflict happen, look for TSR and compare.
-                    mDNSCoreResetRecord(m);
-                    int tsrResult = CheckTSR(m, query, end, q, our);
-                    // Overwrite result if both has TSR
-                    if (tsrResult)
-                    {
-                        result = tsrResult;
-                    }
                 }
                 // If we lost the tie-break for simultaneous probes, we don't immediately give up, because we might be seeing stale packets on the network.
                 // Instead we pause for one second, to give the other host (if real) a chance to establish its name, and then try probing again.
@@ -8281,18 +8819,6 @@
     mDNSCoreResetRecord(m);
 }
 
-// Return mDNStrue when the RR is currently tentative.
-mDNSlocal mDNSBool CheckAndResetRRTentative(mDNS *const m, AuthRecord *const rr)
-{
-    if (rr->Tentative)
-    {
-        if (m->timenow - rr->TentativeSetTime > MaxTentativeSeconds * mDNSPlatformOneSecond)
-        {
-            rr->Tentative = mDNSfalse;
-        }
-    }
-    return rr->Tentative;
-}
 
 // Return mDNStrue if the query is a probe and has an identical record in the authority section.
 // Otherwise return mDNSfalse.
@@ -8315,7 +8841,7 @@
         {
             break;
         }
-        if (m->rec.r.resrec.rrtype != kDNSType_TSR && IdenticalSameNameRecord(&m->rec.r.resrec, &our->resrec))
+        if (IdenticalSameNameRecord(&m->rec.r.resrec, &our->resrec))
         {
             mDNSCoreResetRecord(m);
             result = mDNStrue;
@@ -8327,11 +8853,12 @@
     return result;
 }
 
-// Step1: Compare tsr_value in our TSR AuthRecord and the TSR record in probe, skip conflict check if there is no TSR record;
-// Step2: Check whether resource records in probe confict with our auth record;
-// Return mDNStrue if the tsr_value in probe wins, otherwise return mDNSfalse.
-mDNSlocal mDNSBool ProbeRRConflictAndTSRValueWin(mDNS *const m, const DNSMessage *const query, const mDNSu8 *const end,
-                                                 const DNSQuestion *const q, const AuthRecord *const our)
+// Step1: Compare the TSR AuthRecord and the TSR record in probe,
+//        skip conflict check if there is no TSR record or no hashkey match
+// Step2: Check whether auth names in probe match our auth record;
+// Return mDNStrue if the TSR in probe wins, otherwise return mDNSfalse.
+mDNSlocal mDNSBool ProbeRRMatchAndTSRCheck(mDNS *const m, const DNSMessage *const query, const mDNSu8 *const end,
+    const DNSQuestion *const q, const AuthRecord *const our, const TSROptData *curTSR)
 {
     int i;
     const mDNSu8 *ptr = LocateAuthorities(query, end);
@@ -8343,7 +8870,7 @@
     {
         goto done;
     }
-    if (CheckTSR(m, query, end, q, our) < 0)
+    if (CheckTSRForAuthRecord(m, curTSR, our) == eTSRCheckLose)
     {
         probeTSRWin = mDNStrue;
     }
@@ -8358,14 +8885,12 @@
         {
             break;
         }
-        if (m->rec.r.resrec.rrtype != kDNSType_TSR && our->resrec.rrtype != kDNSType_TSR
-            && PacketRRMatchesSignature(&m->rec.r, our) && !IdenticalSameNameRecord(&m->rec.r.resrec, &our->resrec)
-            && (our->resrec.RecordType & kDNSRecordTypeUniqueMask))
+        if (PacketRRMatchesSignature(&m->rec.r, our) && (our->resrec.RecordType & kDNSRecordTypeUniqueMask))
         {
             mDNSCoreResetRecord(m);
-            LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT, "pkt ar on interface  %p rrtype: " PRI_S ", name: " PRI_DM_NAME PRI_S,
+            LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT, "ProbeRRMatchAndTSRCheck: pkt ar on interface  %p rrtype: " PRI_S ", name: " PRI_DM_NAME PRI_S,
                       q->InterfaceID, DNSTypeName(m->rec.r.resrec.rrtype), DM_NAME_PARAM(m->rec.r.resrec.name), CRDisplayString(m, &m->rec.r));
-            LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT, "Conflict with our ar %p rrtype: " PRI_S ", name: " PRI_DM_NAME PRI_S,
+            LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT, "ProbeRRMatchAndTSRCheck: Conflict with our ar %p rrtype: " PRI_S ", name: " PRI_DM_NAME PRI_S,
                       our->resrec.InterfaceID, DNSTypeName(our->resrec.rrtype), DM_NAME_PARAM(our->resrec.name), ARDisplayString(m, our));
             conflict = mDNStrue;
             break;
@@ -8376,6 +8901,35 @@
     return (conflict && probeTSRWin);
 }
 
+mDNSlocal const mDNSu8 *DomainNamePtrAtTSRIndex(const DNSMessage *const msg, const mDNSu8 *const end, mDNSu16 recIndex)
+{
+    mDNSu16 i = 0;
+    const mDNSu8 *ptr = mDNSNULL;
+    if (msg->h.numAnswers >= recIndex)
+    {
+        ptr = LocateAnswers(msg, end);
+    }
+    else if (msg->h.numAnswers + msg->h.numAuthorities >= recIndex)
+    {
+        ptr = LocateAuthorities(msg, end);
+        i = msg->h.numAnswers;
+    }
+    else if (msg->h.numAnswers + msg->h.numAuthorities + msg->h.numAdditionals >= recIndex)
+    {
+        ptr = LocateAdditionals(msg, end);
+        i = msg->h.numAnswers + msg->h.numAuthorities;
+    }
+    while (ptr && i++ < recIndex)
+    {
+        ptr = skipResourceRecord(msg, ptr, end);
+    }
+    if (ptr >= end)
+    {
+        ptr = mDNSNULL;
+    }
+    return ptr;
+}
+
 mDNSlocal CacheRecord *FindIdenticalRecordInCache(const mDNS *const m, const ResourceRecord *const pktrr)
 {
     CacheGroup *cg = CacheGroupForRecord(m, pktrr);
@@ -8519,13 +9073,15 @@
     const mDNSu8 *ptr;
     mDNSu8       *responseptr        = mDNSNULL;
     AuthRecord   *rr;
+    TSRDataRecHead tsrs = SLIST_HEAD_INITIALIZER(tsrs);
+    const TSROptData *curTSRForName = mDNSNULL;
     int i;
     mdns_assign(outHasResponse, mDNSfalse);
 
     // ***
     // *** 1. Look in Additional Section for an OPT record
     // ***
-    ptr = LocateOptRR(query, end, DNSOpt_OwnerData_ID_Space);
+    ptr = LocateOptRR(query, end, Min(DNSOpt_OwnerData_ID_Space, DNSOpt_TSRData_Space));
     if (ptr)
     {
         ptr = GetLargeResourceRecord(m, query, ptr, end, InterfaceID, kDNSRecordTypePacketAdd, &m->rec);
@@ -8533,14 +9089,48 @@
         {
             const rdataOPT *opt;
             const rdataOPT *const e = (const rdataOPT *)&m->rec.r.resrec.rdata->u.data[m->rec.r.resrec.rdlength];
-            // Find owner sub-option(s). We verify that the MAC is non-zero, otherwise we could inadvertently
-            // delete all our own AuthRecords (which are identified by having zero MAC tags on them).
+            mDNSu8 tsrsCount = 0;
             for (opt = &m->rec.r.resrec.rdata->u.opt[0]; opt < e; opt++)
+            {
+                // Find owner sub-option(s). We verify that the MAC is non-zero, otherwise we could inadvertently
+                // delete all our own AuthRecords (which are identified by having zero MAC tags on them).
                 if (opt->opt == kDNSOpt_Owner && opt->u.owner.vers == 0 && opt->u.owner.HMAC.l[0])
                 {
                     ClearProxyRecords(m, &opt->u.owner, m->DuplicateRecords);
                     ClearProxyRecords(m, &opt->u.owner, m->ResourceRecords);
                 }
+                else if (opt->opt == kDNSOpt_TSR)
+                {
+                    tsrsCount++;
+                    const mDNSu8 *name_ptr;
+                    if ((name_ptr = DomainNamePtrAtTSRIndex(query, end, opt->u.tsr.recIndex)))
+                    {
+                        struct TSRDataRec *newTSR = TSRDataRecCreate(query, name_ptr, end, opt);
+                        if (!newTSR)
+                        {
+                            LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_ERROR,
+                                "ProcessQuery: Create TSR(%u) failed - if %p tsrTime %d tsrHost %x recIndex %d",
+                                tsrsCount, m->rec.r.resrec.InterfaceID, opt->u.tsr.timeStamp, opt->u.tsr.hostkeyHash,
+                                opt->u.tsr.recIndex);
+                            continue;
+                        }
+                        SLIST_INSERT_HEAD(&tsrs, newTSR, entries);
+                    }
+                    else
+                    {
+                        LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_ERROR,
+                            "ProcessQuery: No Domain Name for TSR(%u) if %p tsrTime %d tsrHost %x recIndex %d",
+                            tsrsCount, m->rec.r.resrec.InterfaceID, opt->u.tsr.timeStamp, opt->u.tsr.hostkeyHash,
+                            opt->u.tsr.recIndex);
+                    }
+                }
+            }
+            if (!SLIST_EMPTY(&tsrs))
+            {
+                LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEBUG,
+                    "ProcessQuery: Received TSR(%u) if %p " PUB_S,
+                    tsrsCount, m->rec.r.resrec.InterfaceID, RRDisplayString(m, &m->rec.r.resrec));
+            }
         }
         mDNSCoreResetRecord(m);
     }
@@ -8589,30 +9179,28 @@
         {
             rr = m->CurrentRecord;
             m->CurrentRecord = rr->next;
-            if (AnyTypeRecordAnswersQuestion(rr, &pktq) && (QueryWasMulticast || QueryWasLocalUnicast || rr->AllowRemoteQuery)
-                && !CheckAndResetRRTentative(m, rr))
+            if (AnyTypeRecordAnswersQuestion(rr, &pktq) && (QueryWasMulticast || QueryWasLocalUnicast || rr->AllowRemoteQuery))
             {
                 m->mDNSStats.MatchingAnswersForQueries++;
 
                 const mDNSBool typeMatches = RRTypeAnswersQuestionType(&rr->resrec, pktq.qtype, kRRTypeAnswersQuestionTypeFlagsNone);
                 if (typeMatches)
                 {
+                    curTSRForName = TSRForNameFromDataRec(&tsrs, rr->resrec.name);
                     if (rr->resrec.RecordType == kDNSRecordTypeUnique)
-                        ResolveSimultaneousProbe(m, query, end, &pktq, rr);
+                        ResolveSimultaneousProbe(m, query, end, &pktq, rr, curTSRForName);
                     else if (ProbeHasIdenticalRR(m, query, end, &pktq, rr))
                     {
                         // Don't include this rr in response if this is a probe, and it's authority section has an identical RR.
                         continue;
                     }
-                    else if (ProbeRRConflictAndTSRValueWin(m, query, end, &pktq, rr))
+                    else if (ProbeRRMatchAndTSRCheck(m, query, end, &pktq, rr, curTSRForName))
                     {
-                        // Set this rr as tentative if it's conflicting with the records in probe's authority section and we lose in TSR comparison.
-                        // Don't include this rr in response in this case.
                         LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
-                                  "set tentative for " PRI_DM_NAME " type " PUB_S " on interface id: %p due to TSR lose",
-                                  DM_NAME_PARAM(rr->resrec.name), DNSTypeName(rr->resrec.rrtype), rr->resrec.InterfaceID);
-                        rr->Tentative = mDNStrue;
-                        rr->TentativeSetTime = m->timenow;
+                            "ProcessQuery - deregistering " PRI_DM_NAME " type " PUB_S " on interface id: %p due to TSR conflict",
+                            DM_NAME_PARAM(rr->resrec.name), DNSTypeName(rr->resrec.rrtype), rr->resrec.InterfaceID);
+                        mDNS_Deregister_internal(m, rr, mDNS_Dereg_stale);
+                        continue;
                     }
                     else if (ResourceRecordIsValidAnswer(rr))
                     {
@@ -9012,6 +9600,7 @@
         debugf("ProcessQuery: Recorded DSI for %##s (%s) on %p/%s", q->qname.c, DNSTypeName(q->qtype), InterfaceID,
                srcaddr->type == mDNSAddrType_IPv4 ? "v4" : "v6");
     }
+    TSRDataRecHeadFreeList(&tsrs);
     return(responseptr);
 }
 
@@ -9205,6 +9794,53 @@
     }
 }
 
+mDNSlocal void AddOrUpdateTSRForCacheGroup(mDNS *const m, const TSROptData *curTSROpt, CacheGroup *const cg,
+    CacheRecord *ourTsr, mDNSu32 ttl)
+{
+    mDNSs32 timestampContinuous;
+    mDNSBool new = mDNSfalse;
+    if (!getValidContinousTSRTime(&timestampContinuous, curTSROpt->timeStamp))
+    {
+        LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_ERROR,
+            "AddOrUpdateTSRForCacheGroup: tsrTimestamp[%u] out of range (%u) on TSR for " PRI_DM_NAME "",
+            curTSROpt->timeStamp, MaxTimeSinceReceived, DM_NAME_PARAM(cg->name));
+        return;
+    }
+
+    if (!ourTsr)
+    {
+        ourTsr = GetCacheRecord(m, cg, DNSOpt_TSRData_Space);
+        if (!ourTsr)
+        {
+            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
+                "AddOrUpdateTSRForCacheGroup: No cache record for new TSR " PRI_DM_NAME, DM_NAME_PARAM(cg->name));
+            return;
+        }
+        ourTsr->resrec.rrclass          = NormalMaxDNSMessageData;
+        ourTsr->resrec.rrtype           = kDNSType_OPT;
+        ourTsr->resrec.name             = cg->name;
+        ourTsr->resrec.namehash         = cg->namehash;
+        ourTsr->resrec.rdlength         = DNSOpt_TSRData_Space;
+        ourTsr->resrec.rdestimate       = DNSOpt_TSRData_Space;
+        AddCacheRecordToCacheGroup(cg, ourTsr);
+        new = mDNStrue;
+    }
+    ourTsr->TimeRcvd                = m->timenow;
+    ourTsr->resrec.rroriginalttl    = Max(ourTsr->resrec.rroriginalttl, ttl);
+
+    rdataOPT * const rdata = &ourTsr->resrec.rdata->u.opt[0];
+    if (new || timestampContinuous - rdata->u.tsr.timeStamp > 0) // Always enter if new
+    {
+        rdata->opt                  = kDNSOpt_TSR;
+        rdata->optlen               = DNSOpt_TSRData_Space - 4;
+        rdata->u.tsr.timeStamp      = timestampContinuous;
+        rdata->u.tsr.hostkeyHash    = curTSROpt->hostkeyHash;
+        rdata->u.tsr.recIndex       = 0;
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEBUG,
+            "AddOrUpdateTSRForCacheGroup: %s TSR " PRI_S, new ? "Added" : "Updated", CRDisplayString(m, ourTsr));
+    }
+}
+
 mDNSexport CacheRecord * CreateNewCacheEntryEx(mDNS *const m, const mDNSu32 slot, CacheGroup *cg, const mDNSs32 delay,
     const mDNSBool add, const mDNSAddr *const sourceAddress, const CreateNewCacheEntryFlags flags)
 {
@@ -9292,7 +9928,7 @@
             m->rrcache_totalused_unicast += rr->resrec.rdlength;
         }
 
-#if MDNSRESPONDER_SUPPORTS(COMMON, DNS_PUSH)
+#if MDNSRESPONDER_SUPPORTS(COMMON, DNS_PUSH) || MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
         if (flags & kCreateNewCacheEntryFlagsDNSPushSubscribed)
         {
             rr->DNSPushSubscribed = mDNStrue;
@@ -9309,6 +9945,15 @@
         {
             AddCacheRecordToCacheGroup(cg, rr);
             CacheRecordAdd(m, rr);  // CacheRecordAdd calls SetNextCacheCheckTimeForRecord(m, rr); for us
+#if MDNSRESPONDER_SUPPORTS(APPLE, UNICAST_ASSIST)
+            if (mDNSAddressIsValidNonZero(&rr->sourceAddress)   &&
+                !mDNSAddrIsDNSMulticast(&rr->sourceAddress)     &&
+                mDNS_AddressIsLocalSubnet(m, rr->resrec.InterfaceID, &rr->sourceAddress))
+            {
+                unicast_assist_addr_add(rr->resrec.name, rr->resrec.namehash, rr->resrec.rrtype, rr->resrec.RecordType,
+                    &rr->sourceAddress, rr->resrec.InterfaceID);
+            }
+#endif
         }
         else
         {
@@ -9360,14 +10005,24 @@
 #if MDNSRESPONDER_SUPPORTS(APPLE, UNICAST_ASSIST)
     if (rr->CRActiveQuestion && mDNSOpaque16IsZero(rr->CRActiveQuestion->TargetQID))
     {
+        const mDNSBool sourceAddrValidNonZero = mDNSAddressIsValidNonZero(&rr->sourceAddress);
+        const mDNSBool sourceAddrIsDNSMulticast = mDNSAddrIsDNSMulticast(&rr->sourceAddress);
 #if MDNSRESPONDER_SUPPORTS(APPLE, UNICAST_ASSIST_ANALYTICS)
-        if (rr->LastUnansweredTime != 0 && mDNSAddressIsValidNonZero(&rr->sourceAddress))
+        if (rr->LastUnansweredTime != 0                 &&
+            sourceAddrValidNonZero)
         {
             const bool is_assist    = (rr->unicastAssistSent != mDNSfalse);
-            const bool is_unicast   = (rr->UnansweredQueries == 0 && !mDNSAddrIsDNSMulticast(&rr->sourceAddress));
+            const bool is_unicast   = (rr->UnansweredQueries == 0 && !sourceAddrIsDNSMulticast);
             dnssd_analytics_update_unicast_assist(is_assist, is_unicast);
         }
 #endif
+        if (sourceAddrValidNonZero                      &&
+            !sourceAddrIsDNSMulticast                   &&
+            mDNS_AddressIsLocalSubnet(m, rr->resrec.InterfaceID, &rr->sourceAddress))
+        {
+            unicast_assist_addr_refresh(rr->resrec.name, rr->resrec.namehash, rr->resrec.rrtype, rr->resrec.RecordType,
+                &rr->sourceAddress, rr->resrec.InterfaceID);
+        }
         rr->unicastAssistSent = mDNSfalse;
     }
 #endif
@@ -9375,6 +10030,23 @@
     rr->resrec.rroriginalttl = ttl;
     rr->UnansweredQueries = 0;
     if (rr->resrec.mortality != Mortality_Mortal) rr->resrec.mortality = Mortality_Immortal;
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER) && \
+    (MDNSRESPONDER_SUPPORTS(COMMON, DNS_PUSH) || MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH))
+    const mdns_dns_service_t newService = mdns_cache_metadata_get_dns_service(m->rec.r.resrec.metadata);
+    const mdns_dns_service_t oldService = mdns_cache_metadata_get_dns_service(rr->resrec.metadata);
+    const mDNSBool newRecordComesFromPushService = (newService &&
+        (mdns_dns_service_get_type(newService) == mdns_dns_service_type_push));
+    if (newRecordComesFromPushService && (newService == oldService))
+    {
+        // A cached record that has been unsubscribed and that comes from the same DNS push service is being refreshed
+        // by the same subscription response, so we just need to resubscribe it.
+        mdns_cache_metadata_set_subscriber_id(rr->resrec.metadata,
+            mdns_cache_metadata_get_subscriber_id(m->rec.r.resrec.metadata));
+        rr->DNSPushSubscribed = mDNStrue;
+    }
+#endif
+
     SetNextCacheCheckTimeForRecord(m, rr);
 }
 
@@ -9695,6 +10367,51 @@
 
 #endif // MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+mDNSlocal mDNSBool IsResponseMDNSEquivalent(const mdns_client_t client, const mdns_dns_service_t service)
+{
+    // Determine whether the response is mDNS like response, as opposed to non-mDNS.
+    mDNSBool ResponseIsMDNSEquivalent;
+    const mDNSBool usesQuerier = (mdns_querier_downcast(client) != mDNSNULL);
+    const mDNSBool usesSubscriber = (mdns_subscriber_downcast(client) != mDNSNULL);
+
+    if (usesQuerier)
+    {
+        // Case 1: Message came from a querier, which is inherently non-mDNS, i.e., Do53, DoT, DoH, or ODoH.
+        ResponseIsMDNSEquivalent = mDNSfalse;
+    }
+    else if (usesSubscriber)
+    {
+        // Case 2: Message came from a subscriber, which means it came via DNS Push. Technically, not mDNS, but we may
+        // or may not want to treat messages like mDNS.
+        if (!service)
+        {
+            // Case 2.1: Message came from a subscriber, but no dns service is being used, the record came from a
+            // unicast discovery proxy.
+            ResponseIsMDNSEquivalent = mDNStrue;
+        }
+        else if (mdns_dns_service_is_mdns_alternative(service))
+        {
+            // Case 2.2: Message came from a subscriber, the record is mDNS alternative. (custom DNS push case).
+            ResponseIsMDNSEquivalent = mDNStrue;
+        }
+        else
+        {
+            // Case 2.2: Message came from a subscriber, the record is non-mDNS (discovered DNS push or configured
+            // unicast push service case).
+            ResponseIsMDNSEquivalent = mDNSfalse;
+        }
+    }
+    else
+    {
+        // Case 3: No object provided the message, meaning it came via the mDNS protocol, so this is definitely an mDNS
+        // message.
+        ResponseIsMDNSEquivalent = mDNStrue;
+    }
+    return ResponseIsMDNSEquivalent;
+}
+#endif
+
 mDNSlocal void mDNSCoreReceiveNoUnicastAnswers(mDNS *const m, const DNSMessage *const response, const mDNSu8 *end,
     const mDNSAddr *dstaddr, const mDNSIPPort dstport, const mDNSInterfaceID InterfaceID,
 #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
@@ -9725,12 +10442,7 @@
     mDNSBool hasParsed = mDNSfalse;
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
-    // Determine whether the response is mDNS, as opposed to DNS.
-    // Thus far, the code has assumed that responses with IDs set to zero are mDNS responses. However, this condition
-    // isn't sufficient because queriers, which are used exclusively for DNS queries, may set the IDs of their queries
-    // to zero. And consequently, their responses may have their IDs set to zero. Specifically, zero-valued IDs are used
-    // for DNS over HTTPs, as specified by <https://tools.ietf.org/html/rfc8484#section-4.1>.
-    const mDNSBool ResponseIsMDNS = mDNSOpaque16IsZero(response->h.id) && !querier;
+    const mDNSBool ResponseIsMDNS = IsResponseMDNSEquivalent(mdns_client_upcast(querier), uDNSService);
 #else
     const mDNSBool ResponseIsMDNS = mDNSOpaque16IsZero(response->h.id);
 #endif
@@ -9831,7 +10543,8 @@
                     if (querier)
                     {
                         const mdns_dns_service_t dnsservice = mdns_cache_metadata_get_dns_service(cr->resrec.metadata);
-                        isAnswer = (dnsservice == uDNSService) && Querier_SameNameCacheRecordIsAnswer(cr, querier);
+                        isAnswer = (dnsservice == uDNSService) && Client_SameNameCacheRecordIsAnswer(cr,
+                            mdns_client_upcast(querier));
                     }
                     else
                 #endif
@@ -9914,7 +10627,8 @@
                 if (querier)
                 {
                     const mdns_dns_service_t dnsservice = mdns_cache_metadata_get_dns_service(cr->resrec.metadata);
-                    isAnswer = (dnsservice == uDNSService) && Querier_SameNameCacheRecordIsAnswer(cr, querier);
+                    isAnswer = (dnsservice == uDNSService) && Client_SameNameCacheRecordIsAnswer(cr,
+                        mdns_client_upcast(querier));
                 }
                 else
 #endif
@@ -10446,7 +11160,20 @@
     {
         mDNSBool match;
         // Resource record received via unicast, the resGroupID should match ?
-        if (!InterfaceID)
+        mDNSBool requireMatchedDNSService = !InterfaceID;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+        if (!requireMatchedDNSService)
+        {
+            // If the associated DNS service has local purview and not mDNS-alternative, then it is a record resolved
+            // through non-mDNS, although it has non-zero interface ID, it always has a specific DNS service to be
+            // matched. In which case, we still need to check if the cached record has a matched DNS service with the
+            // newly received record.
+            const mdns_dns_service_t newRecordService = mdns_cache_metadata_get_dns_service(m->rec.r.resrec.metadata);
+            requireMatchedDNSService = (newRecordService && mdns_dns_service_has_local_purview(newRecordService) &&
+                                        !mdns_dns_service_is_mdns_alternative(newRecordService));
+        }
+#endif
+        if (requireMatchedDNSService)
         {
 #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
             match = mdns_cache_metadata_same_dns_service(cr->resrec.metadata, m->rec.r.resrec.metadata);
@@ -10608,23 +11335,17 @@
                         {
                             ptrDomain = &resrec->rdata->u.name;
                         }
-                        const mDNSu32 ptrNameHash = (ptrDomain != mDNSNULL) ?
-                            mDNS_NonCryptoHash(mDNSNonCryptoHash_FNV1a, ptrDomain->c, DomainNameLength(ptrDomain)) : 0;
-
-                        char timestamp[MIN_TIMESTAMP_STRING_LENGTH];
-                        getLocalTimestampFromPlatformTime(m->timenow, cr->TimeRcvd, timestamp, sizeof(timestamp));
-
-                        const char *const ifName = InterfaceNameForID(m, InterfaceID);
-
+                        const mDNSu32 nameHash =  mDNS_DomainNameFNV1aHash(resrec->name);
+                        const mDNSu32 ptrNameHash = (ptrDomain != mDNSNULL) ? mDNS_DomainNameFNV1aHash(ptrDomain) : 0;
+                        struct timeval now;
+                        mDNSGetTimeOfDay(&now, mDNSNULL);
+                        const mDNSu32 ifIndex = mDNSPlatformInterfaceIndexfromInterfaceID(m, InterfaceID, mDNStrue);
                         LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
-                            "Received Goodbye packet for cached record - "
-                            "name: " PRI_DM_NAME "(%x), type: " PUB_S ", last time received: " PUB_S
-                            ", interface: " PUB_S ", source address: " PRI_IP_ADDR ", "
-                            "RDATA domain if PTR: " PRI_DM_NAME ", name hash if PTR: %x",
-                            DM_NAME_PARAM(resrec->name),
-                            mDNS_NonCryptoHash(mDNSNonCryptoHash_FNV1a, resrec->name->c, DomainNameLength(resrec->name)),
-                            DNSTypeName(resrec->rrtype), timestamp, ifName,
-                            &cr->sourceAddress, DM_NAME_PARAM(ptrDomain), ptrNameHash);
+                            "Received Goodbye packet for cached record -- "
+                            "name hash: %x, type: " PUB_DNS_TYPE ", last time received: " PUB_TIMEV
+                            ", interface index: %d, source address: " PRI_IP_ADDR ", name hash if PTR: %x",
+                            nameHash, DNS_TYPE_PARAM(resrec->rrtype), TIMEV_PARAM(&now), ifIndex, &cr->sourceAddress,
+                            ptrNameHash);
 
                         cr->resrec.rroriginalttl = 1;
                         cr->TimeRcvd = m->timenow;
@@ -10804,26 +11525,27 @@
     int firstauthority  =                   response->h.numAnswers;
     int firstadditional = firstauthority  + response->h.numAuthorities;
     int totalrecords    = firstadditional + response->h.numAdditionals;
-    const mDNSu8 *ptr   = response->data;
+    const mDNSu8 *ptr   = mDNSNULL;
 #if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     DNSServer *uDNSServer = mDNSNULL;
 #endif
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     mdns_cache_metadata_t cache_metadata = mDNSNULL;
-    // Determine whether the response is mDNS, as opposed to DNS.
-    // Thus far, the code has assumed that responses with IDs set to zero are mDNS responses. However, this condition
-    // isn't sufficient because queriers, which are used exclusively for DNS queries, may set the IDs of their queries
-    // to zero. And consequently, their responses may have their IDs set to zero. Specifically, zero-valued IDs are used
-    // for DNS over HTTPs, as specified by <https://tools.ietf.org/html/rfc8484#section-4.1>.
     const mdns_querier_t querier = mdns_querier_downcast(client);
-    const mDNSBool ResponseIsMDNS = mDNSOpaque16IsZero(response->h.id) && !querier;
+    const mdns_subscriber_t subscriber = mdns_subscriber_downcast(client);
+    const mDNSBool ResponseIsMDNS = IsResponseMDNSEquivalent(client, uDNSService);
+    const mDNSBool subscriberResponse = (subscriber != mDNSNULL);
 #else
     const mDNSBool ResponseIsMDNS = mDNSOpaque16IsZero(response->h.id);
+    const mDNSBool subscriberResponse = mDNSfalse;
 #endif
 
     mDNSBool dumpMDNSPacket = mDNSfalse;
 
+    TSRDataRecHead tsrs = SLIST_HEAD_INITIALIZER(tsrs);
+    const TSROptData *curTSRForName = mDNSNULL;
+
     debugf("Received Response from %#-15a addressed to %#-15a on %p with "
            "%2d Question%s %2d Answer%s %2d Authorit%s %2d Additional%s %d bytes LLQType %d",
            srcaddr, dstaddr, InterfaceID,
@@ -10863,7 +11585,7 @@
     //    datagram.  Thus if there is any data for the authority section, the
     //    answer section is guaranteed to be unique.
 #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
-    if (!InterfaceID && (response->h.flags.b[0] & kDNSFlag0_TC) && !querier &&
+    if (!InterfaceID && (response->h.flags.b[0] & kDNSFlag0_TC) && !client &&
 #else
     if (!InterfaceID && (response->h.flags.b[0] & kDNSFlag0_TC) &&
 #endif
@@ -10874,6 +11596,54 @@
 
     mdns_require_quiet(LLQType != uDNS_LLQ_Ignore, exit);
 
+    // Look in Additional Section for an OPT record
+    ptr = LocateOptRR(response, end, DNSOpt_TSRData_Space);
+    if (ptr)
+    {
+        ptr = GetLargeResourceRecord(m, response, ptr, end, InterfaceID, kDNSRecordTypePacketAdd, &m->rec);
+        if (ptr && m->rec.r.resrec.RecordType != kDNSRecordTypePacketNegative && m->rec.r.resrec.rrtype == kDNSType_OPT)
+        {
+            const rdataOPT *opt;
+            const rdataOPT *const e = (const rdataOPT *)&m->rec.r.resrec.rdata->u.data[m->rec.r.resrec.rdlength];
+            mDNSu8 tsrsCount = 0;
+            for (opt = &m->rec.r.resrec.rdata->u.opt[0]; opt < e; opt++)
+            {
+                if (opt->opt == kDNSOpt_TSR)
+                {
+                    tsrsCount++;
+                    const mDNSu8 *name_ptr;
+                    if ((name_ptr = DomainNamePtrAtTSRIndex(response, end, opt->u.tsr.recIndex)))
+                    {
+                        struct TSRDataRec *newTSR = TSRDataRecCreate(response, name_ptr, end, opt);
+                        if (!newTSR)
+                        {
+                            LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_ERROR,
+                                "mDNSCoreReceiveResponse: Create TSR(%u) failed - if %p tsrTime %d tsrHost %x recIndex %d",
+                                tsrsCount, m->rec.r.resrec.InterfaceID, opt->u.tsr.timeStamp, opt->u.tsr.hostkeyHash,
+                                opt->u.tsr.recIndex);
+                            continue;
+                        }
+                        SLIST_INSERT_HEAD(&tsrs, newTSR, entries);
+                    }
+                    else
+                    {
+                        LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_ERROR,
+                            "mDNSCoreReceiveResponse: No Domain Name for TSR(%u) if %p tsrTime %d tsrHost %x recIndex %d",
+                            tsrsCount, m->rec.r.resrec.InterfaceID, opt->u.tsr.timeStamp, opt->u.tsr.hostkeyHash,
+                            opt->u.tsr.recIndex);
+                    }
+                }
+            }
+            if (!SLIST_EMPTY(&tsrs))
+            {
+                LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEBUG,
+                    "mDNSCoreReceiveResponse: Received TSR(%u) if %p " PUB_S,
+                    tsrsCount, m->rec.r.resrec.InterfaceID, RRDisplayString(m, &m->rec.r.resrec));
+            }
+       }
+        mDNSCoreResetRecord(m);
+    }
+
     // 1. We ignore questions (if any) in mDNS response packets
     // 2. If this is an LLQ response, we handle it much the same
     // Otherwise, this is a authoritative uDNS answer, so arrange for any stale records to be purged
@@ -10887,6 +11657,7 @@
         const int rcode = response->h.flags.b[1] & kDNSFlag1_RC_Mask;
         failure = !(rcode == kDNSFlag1_RC_NoErr || rcode == kDNSFlag1_RC_NXDomain || rcode == kDNSFlag1_RC_NotAuth);
         returnEarly = mDNSfalse;
+        ptr = response->data;
 #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
         // When the QUERIER functionality is enabled, DNS transport is handled exclusively by querier objects. If this
         // response was provided by a querier, but the RCODE is considered a failure, then set failure to false so that
@@ -10907,18 +11678,22 @@
         // which it was received (or refreshed), and then at the end if we find any cache records which
         // answer questions in this packet's question section, but which aren't tagged with this packet's
         // packet number, then we deduce they are old and delete them
+        // Additional condition to check: a response comes from subscriber does not flush previous records, so skip it.
+        //
+        // Note: the for loop below must be executed even when we don't need to flush previous records, because we use
+        // "getQuestion" to advance the response read pointer.
         for (i = 0; i < response->h.numQuestions && ptr && ptr < end; i++)
         {
             DNSQuestion q;
             DNSQuestion *qptr;
             mDNSBool expectingResponse;
             ptr = getQuestion(response, ptr, end, InterfaceID, &q);
-            if (!ptr)
+            if (!ptr || subscriberResponse)
             {
                 continue;
             }
 #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
-            if (querier)
+            if (client)
             {
                 expectingResponse = mDNStrue;
                 qptr = mDNSNULL;
@@ -10941,17 +11716,22 @@
                 {
                     mDNSBool isAnswer;
 #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
-                    if (querier)
+                    if (client)
                     {
                         const mdns_dns_service_t dnsservice = mdns_cache_metadata_get_dns_service(cr->resrec.metadata);
-                        isAnswer = (dnsservice == uDNSService) && Querier_SameNameCacheRecordIsAnswer(cr, querier);
+                        isAnswer = (dnsservice == uDNSService) && Client_SameNameCacheRecordIsAnswer(cr, client);
                     }
                     else
 #endif
                     {
                         isAnswer = SameNameCacheRecordAnswersQuestion(cr, qptr);
                     }
-                    if (isAnswer)
+                    mDNSBool flushable = isAnswer;
+                #if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
+                    // Push subscribed records will never be flushed by a response (except "remove" push notification).
+                    flushable = flushable && (!cr->DNSPushSubscribed);
+                #endif
+                    if (flushable)
                     {
                         LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEBUG,
                             "Making record answered by the current response as expired if it is not refreshed in the response - "
@@ -11058,7 +11838,7 @@
         // We accept all records in a unicast response to a multicast query once we find one that
         // answers an active question.
 #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
-        mDNSBool AcceptableResponse = ResponseMCast || (!querier && !dstaddr) || LLQType || recordAcceptedInResponse;
+        mDNSBool AcceptableResponse = ResponseMCast || (!client && !dstaddr) || LLQType || recordAcceptedInResponse;
 #else
         mDNSBool AcceptableResponse = ResponseMCast || !dstaddr || LLQType || recordAcceptedInResponse;
 #endif
@@ -11071,8 +11851,21 @@
         ptr = GetLargeResourceRecord(m, response, ptr, end, InterfaceID, RecordType, &m->rec);
         mdns_require_quiet(ptr, bail); // Break out of the loop and clean up our CacheFlushRecords list before exiting
 
-#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
-        const mdns_subscriber_t subscriber = mdns_subscriber_downcast(client);
+    #if MDNSRESPONDER_SUPPORTS(APPLE, DISCOVERY_PROXY_CLIENT)
+        // If the message being processed is a true mDNS message, i.e., it was received via the mDNS protocol, then
+        // don't cache the record if there's already a Discovery Proxy subscription for the resource record set because
+        // the Discovery Proxy is our current source of truth.
+        if (DPCFeatureEnabled() && ResponseIsMDNS && !subscriber)
+        {
+            const ResourceRecord *const rr = &m->rec.r.resrec;
+            if (DPCHaveSubscriberForRecord(InterfaceID, rr->name, rr->rrtype, rr->rrclass))
+            {
+                mDNSCoreResetRecord(m);
+                continue;
+            }
+        }
+    #endif
+    #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
         if (querier || subscriber)
         {
             if (!cache_metadata)
@@ -11100,7 +11893,7 @@
             ResourceRecord *const rr = &m->rec.r.resrec;
             mdns_replace(&rr->metadata, cache_metadata);
         }
-#endif
+    #endif
         if (m->rec.r.resrec.RecordType == kDNSRecordTypePacketNegative)
         {
             mDNSCoreResetRecord(m);
@@ -11179,10 +11972,10 @@
                 // Even though it is AcceptableResponse, we still need a DNSServer pointer for the resource records that
                 // we create.
 #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
-                if (querier)
+                if (client)
                 {
                     ResourceRecord *const rr = &m->rec.r.resrec;
-                    if (Querier_ResourceRecordIsAnswer(rr, querier))
+                    if (Client_ResourceRecordIsAnswer(rr, client))
                     {
                         AcceptableResponse = mDNStrue;
                     }
@@ -11269,7 +12062,7 @@
         }
 
         // 1. Check that this packet resource record does not conflict with any of ours
-        if (ResponseIsMDNS && m->rec.r.resrec.rrtype != kDNSType_NSEC && m->rec.r.resrec.rrtype != kDNSType_TSR)
+        if (ResponseIsMDNS && m->rec.r.resrec.rrtype != kDNSType_NSEC)
         {
             if (m->CurrentRecord)
                 LogMsg("mDNSCoreReceiveResponse ERROR m->CurrentRecord already set %s", ARDisplayString(m, m->CurrentRecord));
@@ -11283,30 +12076,30 @@
                 // (apparently) local source address that pertain to a record of our own that's in probing state
                 if (!AcceptableResponse && !(ResponseSrcLocal && rr->resrec.RecordType == kDNSRecordTypeUnique)) continue;
 
-                // Don't check conflict for TSR record
-                if (rr->resrec.rrtype == kDNSType_TSR) continue;
-
                 if (PacketRRMatchesSignature(&m->rec.r, rr))        // If interface, name, type (if shared record) and class match...
                 {
-                    // check to see if previously marked as tentative
-                    if(CheckAndResetRRTentative(m, rr))
+                    if ((curTSRForName = TSRForNameFromDataRec(&tsrs, m->rec.r.resrec.name)) != mDNSNULL)
                     {
-                        // Having a real conflict, we should log the packet to check why it has conflicts with
-                        // us later.
-                        dumpMDNSPacket = mDNStrue;
-
-                        LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,"mDNSCoreReceiveResponse: tentative is true, ProbeCount %d; "
-                            "will deregister %s due to multicast conflict via interface %d", rr->ProbeCount, ARDisplayString(m, rr),
-                            IIDPrintable(InterfaceID));
-                        m->mDNSStats.NameConflicts++;
+                        eTSRCheckResult tsrResult = CheckTSRForAuthRecord(m, curTSRForName, rr);
+                        if (tsrResult == eTSRCheckLose)
+                        {
+                            LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
+                                "mDNSCoreReceiveResponse - deregistering " PRI_DM_NAME " type " PUB_S " on interface %d due to TSR conflict",
+                                DM_NAME_PARAM(rr->resrec.name), DNSTypeName(rr->resrec.rrtype), (int)IIDPrintable(InterfaceID));
 #if MDNSRESPONDER_SUPPORTS(APPLE, D2D)
-                        // See if this record was also registered with any D2D plugins.
-                        D2D_stop_advertising_record(rr);
+                            // See if this record was also registered with any D2D plugins.
+                            D2D_stop_advertising_record(rr);
 #endif
-                        mDNS_Deregister_internal(m, rr, mDNS_Dereg_conflict);
+                            mDNS_Deregister_internal(m, rr, mDNS_Dereg_stale);
+                        }
+
+                        if (tsrResult != eTSRCheckNoKeyMatch) // No else
+                        {
+                            continue;
+                        }
                     }
                     // ... check to see if type and rdata are identical
-                    else if (IdenticalSameNameRecord(&m->rec.r.resrec, &rr->resrec))
+                    if (IdenticalSameNameRecord(&m->rec.r.resrec, &rr->resrec))
                     {
                         // If the RR in the packet is identical to ours, just check they're not trying to lower the TTL on us
                         if (m->rec.r.resrec.rroriginalttl >= rr->resrec.rroriginalttl/2 || m->SleepState)
@@ -11473,13 +12266,56 @@
 #endif
         }
 
+        CacheGroup *cg = mDNSNULL;
+        if (AcceptableResponse &&
+            (curTSRForName = TSRForNameFromDataRec(&tsrs, m->rec.r.resrec.name)) != mDNSNULL)
+        {
+            cg = CacheGroupForRecord(m, &m->rec.r.resrec);
+            if (cg)
+            {
+                CacheRecord *ourTSR = mDNSGetTSRForCacheGroup(cg);
+                if (ourTSR)
+                {
+                    eTSRCheckResult tsrResult = CheckTSRForResourceRecord(curTSRForName, &ourTSR->resrec);
+                    if (tsrResult == eTSRCheckWin)
+                    {
+                        AcceptableResponse = mDNSfalse;
+                    }
+                    else if (tsrResult == eTSRCheckLose)
+                    {
+                        LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
+                            "mDNSCoreReceiveResponse - flushing cache group " PRI_DM_NAME " type " PUB_S " on interface %d due to TSR conflict",
+                            DM_NAME_PARAM(m->rec.r.resrec.name), DNSTypeName(m->rec.r.resrec.rrtype), (int)IIDPrintable(InterfaceID));
+                        CacheRecord *cr;
+                        for (cr = cg ? cg->members : mDNSNULL; cr; cr=cr->next)
+                        {
+                            if (cr->resrec.rrtype != kDNSType_OPT)
+                            {
+                                mDNS_PurgeCacheResourceRecord(m, cr);
+                                LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
+                                    "mDNSCoreReceiveResponse - flushed interface %d " PRI_S,
+                                    (int)IIDPrintable(cr->resrec.InterfaceID), CRDisplayString(m, cr));
+                            }
+                        }
+                    }
+                }
+                if (AcceptableResponse)
+                {
+                    AddOrUpdateTSRForCacheGroup(m, curTSRForName, cg, ourTSR, m->rec.r.resrec.rroriginalttl);
+                }
+            }
+        }
+
         // 2. See if we want to add this packet resource record to our cache
         // We only try to cache answers if we have a cache to put them in
         // Also, we ignore any apparent attempts at cache poisoning unicast to us that do not answer any outstanding active query
         if (m->rrcache_size && AcceptableResponse)
         {
             const mDNSu32 slot = HashSlotFromNameHash(m->rec.r.resrec.namehash);
-            CacheGroup *cg = CacheGroupForRecord(m, &m->rec.r.resrec);
+            if (!cg)
+            {
+                cg = CacheGroupForRecord(m, &m->rec.r.resrec);
+            }
             CacheRecord *rr = mDNSNULL;
         #if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
             // Set to true if no new cache record has been created for the response, either due to existing cached
@@ -11611,6 +12447,7 @@
         }
         mDNSCoreResetRecord(m);
     }
+    TSRDataRecHeadFreeList(&tsrs);
 #if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
     // After finishing processing all the records in the response, update the corresponding fields for the records
     // in the cache.
@@ -11628,7 +12465,7 @@
             CacheRecord *const record = cachedRecords[k];
 
             // Ensure that the max number of records in a response can be represented by numOfCachedRecords.
-            check_compile_time_code(countof(cachedRecords) < UINT8_MAX);
+            mdns_compile_time_check_local(countof(cachedRecords) < UINT8_MAX);
 
             const RecordSet *recordSet = mDNSNULL;
             for (mDNSu32 kk = 0; kk < numOfRecordSets; kk++)
@@ -11691,11 +12528,15 @@
         // If this were to happen repeatedly, the record's expiration could be deferred indefinitely.
         // To avoid this, we need to ensure that the cache flushing operation will only act to
         // *decrease* a record's remaining lifetime, never *increase* it.
-        for (r2 = cg ? cg->members : mDNSNULL; r2; r2=r2->next)
+        // Additional condition to check: a response comes from subscriber does not flush previous records, so skip it.
+        for (r2 = cg ? cg->members : mDNSNULL; r2 && !subscriberResponse; r2=r2->next)
         {
-
-            mDNSBool proceed = SameNameCacheRecordsMatchInSourceTypeClass(r1, r2);
-
+            mDNSBool proceed = mDNStrue;
+        #if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
+            // Push subscribed records will never be flushed by a response (except "remove" push notification).
+            proceed = proceed && !r2->DNSPushSubscribed;
+        #endif
+            proceed = proceed && SameNameCacheRecordsMatchInSourceTypeClass(r1, r2);
         #if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
             proceed = proceed && resource_records_is_comparable_for_dnssec(&r1->resrec, &r2->resrec);
         #endif
@@ -11734,8 +12575,39 @@
                         if (!(r2->resrec.rroriginalttl == 240 && r1->resrec.rroriginalttl == 60 && r2->resrec.rrtype == kDNSType_TXT) &&
                             !(r2->resrec.rroriginalttl == 120 && r1->resrec.rroriginalttl == 4500 && r2->resrec.rrtype == kDNSType_SRV) &&
                             ResponseIsMDNS)
-                            LogInfo("Correcting TTL from %4d to %4d for %s",
-                                    r2->resrec.rroriginalttl, r1->resrec.rroriginalttl, CRDisplayString(m, r2));
+                        {
+                            static mDNSs32 lastLogWindowStartTime = 0;
+                            static mDNSu32 count = 0;
+
+                            mDNSBool reset = mDNSfalse;
+                            if (lastLogWindowStartTime != 0)
+                            {
+                                const mDNSu32 elapsedTicks = (mDNSu32)(m->timenow - lastLogWindowStartTime);
+                                const mDNSu32 limitTicks = MDNS_SECONDS_PER_HOUR * mDNSPlatformOneSecond;
+                                if (elapsedTicks >= limitTicks)
+                                {
+                                    reset = mDNStrue;
+                                }
+                            }
+                            else
+                            {
+                                reset = mDNStrue;
+                            }
+                            if (reset)
+                            {
+                                count = 0;
+                                lastLogWindowStartTime = NonZeroTime(m->timenow);
+                            }
+
+                            const mDNSu32 ttlCorrectingLogRateLimitCount = 100;
+                            const mDNSBool rateLimiting = (count >= ttlCorrectingLogRateLimitCount);
+                            count++;
+
+                            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, rateLimiting ? MDNS_LOG_DEBUG : MDNS_LOG_INFO,
+                                "Correcting TTL from %4u to %4u from " PRI_IP_ADDR ":%u for records " PRI_S,
+                                r2->resrec.rroriginalttl, r1->resrec.rroriginalttl, srcaddr, mDNSVal16(srcport),
+                                CRDisplayString(m, r2));
+                        }
                         r2->resrec.rroriginalttl = r1->resrec.rroriginalttl;
                     }
                     r2->TimeRcvd = m->timenow;
@@ -11879,6 +12751,15 @@
     }
 #endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    // If this DNS message is constructed from a subscriber with a push service, then skip the negative record handling
+    // below, because push notification never adds negative records.
+    if (subscriber)
+    {
+        goto exit;
+    }
+#endif
+
     // See if we need to generate negative cache entries for unanswered unicast questions
     mDNSCoreReceiveNoUnicastAnswers(m, response, end, dstaddr, dstport, InterfaceID,
 #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
@@ -12930,6 +13811,15 @@
             if (QuestionSendsMDNSQueriesViaUnicast(q))  continue;
         }
 #endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
+        // Only the DNS push enabled question can be dupped to a DNS push enabled question.
+        if (dns_question_uses_dns_push(q) != dns_question_uses_dns_push(question))
+        {
+            continue;
+        }
+#endif
+
         return(q);
     }
     return(mDNSNULL);
@@ -12986,6 +13876,12 @@
                 q->noServerResponse  = question->noServerResponse;
                 q->triedAllServersOnce = question->triedAllServersOnce;
 #endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, DISCOVERY_PROXY_CLIENT)
+                // Duplicate questions aren't eligible to have Discovery Proxy subscribers, so a simple
+                // handoff from the former lead question to the new lead is sufficient.
+                q->DPSubscribers = question->DPSubscribers;
+                question->DPSubscribers = mDNSNULL;
+#endif
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
                 // If this question is a primary DNSSEC question that does the validation work, transfer the work to
@@ -12999,14 +13895,6 @@
                 }
 #endif
 
-#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
-                if (question->dns_push)
-                {
-                    dns_push_obj_replace(&q->dns_push, question->dns_push);
-                    dns_push_obj_forget(&question->dns_push);
-                }
-#endif
-
                 q->TargetQID         = question->TargetQID;
 #if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
                 q->LocalSocket       = question->LocalSocket;
@@ -13993,7 +14881,8 @@
         if (question->TimeoutQuestion && !question->StopTime)
         {
             question->StopTime = NonZeroTime(m->timenow + timeout * mDNSPlatformOneSecond);
-            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "[Q%u] InitDNSConfig: Setting StopTime on the uDNS question %p " PRI_DM_NAME " (" PUB_S ")",
+            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEBUG,
+                "[Q%u] InitDNSConfig: Setting StopTime on the uDNS question %p " PRI_DM_NAME " (" PUB_S ")",
                 mDNSVal16(question->TargetQID), question, DM_NAME_PARAM(&question->qname),
                 DNSTypeName(question->qtype));
         }
@@ -14016,7 +14905,8 @@
         mDNSu32 timeout = LocalOnlyOrP2PInterface(question->InterfaceID) ?
                             DEFAULT_LO_OR_P2P_TIMEOUT : GetTimeoutForMcastQuestion(m, question);
         question->StopTime = NonZeroTime(m->timenow + timeout * mDNSPlatformOneSecond);
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "[R%u->Q%u] InitDNSConfig: Setting StopTime on the uDNS question %p " PRI_DM_NAME " (" PUB_S ")",
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEBUG,
+            "[R%u->Q%u] InitDNSConfig: Setting StopTime on the uDNS question %p " PRI_DM_NAME " (" PUB_S ")",
             question->request_id, mDNSVal16(question->TargetQID), question, DM_NAME_PARAM(&question->qname),
             DNSTypeName(question->qtype));
     }
@@ -14315,6 +15205,19 @@
 #endif
     debugf("mDNS_StartQuery_internal: %##s (%s)", question->qname.c, DNSTypeName(question->qtype));
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, TERMINUS_ASSISTED_UNICAST_DISCOVERY)
+    // Check if the query meets the requirement of using mDNS alternative service and if we have such a service
+    // available.
+    if (DNSQuestionIsEligibleForMDNSAlternativeService(question) &&
+        Querier_IsMDNSAlternativeServiceAvailableForQuestion(question))
+    {
+        // If so, we convert it to a non-mDNS query to exclude any mDNS query.
+        question->TargetQID = mDNS_NewMessageID(m);
+        // After the operation above, DNSQuestionRequestsMDNSAlternativeService returns true indicating that this
+        // question is an mDNS question but requests an mDNS alternative service to be used.
+    }
+#endif
+
 #if MDNSRESPONDER_SUPPORTS(APPLE, LOG_PRIVACY_LEVEL)
     // This code block must:
     // 1. Be put after question ID is assigned.
@@ -14363,10 +15266,12 @@
     }
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH) && !defined(UNIT_TEST)
-    const mDNSBool enableDNSPush = (question->LongLived) && (os_feature_enabled(mDNSResponder, dns_push));
-    // If the `dns_push` member variable in the question is nonnull, then it is a restarted DNS push enabled question,
-    // so skip the dns push bootstrap process.
-    if (!question->dns_push && enableDNSPush)
+    // If the question has requested using DNS push, check if it already has DNS push context initialized:
+    // 1. If it is initialized, then it is a restarted DNS push question, so we do not reinitialize DNS push context.
+    // 2. If it is uninitialized, then initialize it and start DNS push bootstrapping.
+    if (dns_question_enables_dns_push(question) &&
+        !Querier_IsCustomPushServiceAvailableForQuestion(question) &&
+        !dns_question_uses_dns_push(question))
     {
         const dns_obj_error_t dns_push_err = dns_push_handle_question_start(m, question);
         mdns_require_noerr_action_quiet(dns_push_err, exit, err = mStatus_BadParamErr);
@@ -14398,6 +15303,17 @@
         mdns_require_noerr_action_quiet(dnssec_err, exit, err = mStatus_UnknownErr);
     }
 #endif
+    if (question->request_id == 0)
+    {
+        // After: [Q0] mDNS_StartQuery_internal START -- qname: 70-35-60-63\134.1\134 joey-test-atv._sleep-proxy._udp.local., qtype: SRV
+        // request_id == 0 indicates that the query is started by mDNSResponder itself, in which case no request
+        // level log is available. Therefore, we need to print the query start log message to indicate that
+        // mDNSResponder has started a query internally.
+        LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
+            "[Q%u] mDNS_StartQuery_internal START -- qname: " PRI_DM_NAME " (%x), qtype: " PUB_DNS_TYPE,
+            mDNSVal16(question->TargetQID), DM_NAME_PARAM_NONNULL(&question->qname),
+            mDNS_DomainNameFNV1aHash(&question->qname), DNS_TYPE_PARAM(question->qtype));
+    }
     if (!localOnlyOrP2P)
     {
         // If the question's id is non-zero, then it's Wide Area
@@ -14414,10 +15330,6 @@
         {
 #if MDNSRESPONDER_SUPPORTS(APPLE, BONJOUR_ON_DEMAND)
             m->NumAllInterfaceQuestions++;
-            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "mDNS_StartQuery_internal: "
-                "NumAllInterfaceRecords %u NumAllInterfaceQuestions %u " PRI_DM_NAME " (" PUB_S ")",
-                m->NumAllInterfaceRecords, m->NumAllInterfaceQuestions, DM_NAME_PARAM(&question->qname),
-                DNSTypeName(question->qtype));
             if (m->NumAllInterfaceRecords + m->NumAllInterfaceQuestions == 1)
             {
                 m->NextBonjourDisableTime = 0;
@@ -14432,7 +15344,9 @@
 #endif
             if (question->WakeOnResolve)
             {
-                LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "mDNS_StartQuery_internal: Purging for " PRI_DM_NAME, DM_NAME_PARAM(&question->qname));
+                LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
+                    "[Q%u] mDNS_StartQuery_internal: Purging records before resolving",
+                    mDNSVal16(question->TargetQID));
                 mDNS_PurgeBeforeResolve(m, question);
             }
         }
@@ -14481,7 +15395,7 @@
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH) && !defined(UNIT_TEST)
     // Do not stop DNS push activity if we are restarting this question.
-    if (m->RestartQuestion != question && dns_question_enables_dns_push(question))
+    if (m->RestartQuestion != question && dns_question_uses_dns_push(question))
     {
         dns_push_handle_question_stop(m, question);
     }
@@ -14518,12 +15432,17 @@
         if (m->NumAllInterfaceRecords + m->NumAllInterfaceQuestions == 1)
             m->NextBonjourDisableTime = NonZeroTime(m->timenow + (BONJOUR_DISABLE_DELAY * mDNSPlatformOneSecond));
         m->NumAllInterfaceQuestions--;
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
-            "mDNS_StopQuery_internal: NumAllInterfaceRecords %u NumAllInterfaceQuestions %u " PRI_DM_NAME " (" PUB_S ")",
-            m->NumAllInterfaceRecords, m->NumAllInterfaceQuestions, DM_NAME_PARAM(&question->qname),
-            DNSTypeName(question->qtype));
     }
 #endif
+    if (question->request_id == 0)
+    {
+        // request_id == 0 indicates that the query is stopped by mDNSResponder itself, in which case no request
+        // level log is available. Therefore, we need to print the query stop log message to indicate that
+        // mDNSResponder has stopped a query internally.
+        LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
+            "[Q%u] mDNS_StopQuery_internal STOP -- name hash: %x",
+            mDNSVal16(question->TargetQID), mDNS_DomainNameFNV1aHash(&question->qname));
+    }
 
     // Take care to cut question from list *before* calling UpdateQuestionDuplicates
     UpdateQuestionDuplicates(m, question);
@@ -14660,6 +15579,10 @@
     Querier_HandleStoppedDNSQuestion(question);
 #endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, DISCOVERY_PROXY_CLIENT)
+    DPCHandleStoppedDNSQuestion(question);
+#endif
+
 #if MDNSRESPONDER_SUPPORTS(APPLE, DNS_ANALYTICS) || MDNSRESPONDER_SUPPORTS(APPLE, RUNTIME_MDNS_METRICS)
     DNSMetricsClear(&question->metrics);
 #endif
@@ -14859,9 +15782,10 @@
     if (question->request_id != 0)
     {
         // This is the first place where we know the full name of a browsing query.
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "[R%u] DNSServiceBrowse - SubBrowser(\"" PRI_DM_NAME
-            "\"(%x)) START", question->request_id, DM_NAME_PARAM(&question->qname),
-            mDNS_NonCryptoHash(mDNSNonCryptoHash_FNV1a, question->qname.c, DomainNameLength(&question->qname)));
+        // After: [R6] DNSServiceBrowse -> SubBrowser START -- qname: _companion-link._tcp.local.(334e2060)
+        LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
+            "[R%u] DNSServiceBrowse -> SubBrowser START -- qname: " PRI_DM_NAME " (%x)",
+            question->request_id, DM_NAME_PARAM(&question->qname), mDNS_DomainNameFNV1aHash(&question->qname));
     }
 
     return(mDNS_StartQuery_internal(m, question));
@@ -15031,6 +15955,9 @@
                 enumeration->state = DomainEnumerationState_Started;
             }
             break;
+
+        MDNS_COVERED_SWITCH_DEFAULT:
+            break;
     }
 
     err = mStatus_NoError;
@@ -15277,8 +16204,8 @@
     }
 #endif
 
-    if (RRLocalOnly(rr) || (rr->resrec.rrtype == kDNSType_TSR) || (rr->resrec.rroriginalttl == newttl &&
-                            rr->resrec.rdlength == newrdlength && mDNSPlatformMemSame(rr->resrec.rdata->u.data, newrdata->u.data, newrdlength)))
+    if (RRLocalOnly(rr) || (rr->resrec.rroriginalttl == newttl && rr->resrec.rdlength == newrdlength &&
+                            mDNSPlatformMemSame(rr->resrec.rdata->u.data, newrdata->u.data, newrdlength)))
         CompleteRDataUpdate(m, rr);
     else
     {
@@ -15684,6 +16611,7 @@
         }
 }
 
+#if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
 mDNSlocal void InitializeNetWakeState(mDNS *const m, NetworkInterfaceInfo *set)
 {
     int i;
@@ -15702,9 +16630,11 @@
     set->NextSPSAttempt     = -1;
     set->NextSPSAttemptTime = m->timenow;
 }
+#endif
 
 mDNSexport void mDNS_ActivateNetWake_internal(mDNS *const m, NetworkInterfaceInfo *set)
 {
+#if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
     NetworkInterfaceInfo *p = m->HostInterfaces;
     while (p && p != set) p=p->next;
     if (!p)
@@ -15718,10 +16648,15 @@
         LogRedact(MDNS_LOG_CATEGORY_SPS, MDNS_LOG_DEFAULT, "ActivateNetWake for " PUB_S " (" PRI_IP_ADDR ")", set->ifname, &set->ip);
         mDNS_StartBrowse_internal(m, &set->NetWakeBrowse, &SleepProxyServiceType, &localdomain, set->InterfaceID, 0, mDNSfalse, mDNSfalse, m->SPSBrowseCallback, set);
     }
+#else
+    (void)m;
+    (void)set;
+#endif
 }
 
 mDNSexport void mDNS_DeactivateNetWake_internal(mDNS *const m, NetworkInterfaceInfo *set)
 {
+#if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
     NetworkInterfaceInfo *p = m->HostInterfaces;
     while (p && p != set) p=p->next;
     if (!p)
@@ -15755,6 +16690,22 @@
         // (includes resetting NetWakeBrowse.ThisQInterval back to -1)
         InitializeNetWakeState(m, set);
     }
+#else
+    (void)m;
+    (void)set;
+#endif
+}
+
+mDNSlocal mDNSBool IsInterfaceValidForQuestion(const DNSQuestion *const q, const NetworkInterfaceInfo *const intf)
+{
+    if (q->InterfaceID == mDNSInterface_Any)
+    {
+        return mDNSPlatformValidQuestionForInterface(q, intf);
+    }
+    else
+    {
+        return (q->InterfaceID == intf->InterfaceID);
+    }
 }
 
 mDNSexport mStatus mDNS_RegisterInterface(mDNS *const m, NetworkInterfaceInfo *set, InterfaceActivationSpeed activationSpeed)
@@ -15784,7 +16735,9 @@
     set->IPv4Available   = (mDNSu8)(set->ip.type == mDNSAddrType_IPv4 && set->McastTxRx);
     set->IPv6Available   = (mDNSu8)(set->ip.type == mDNSAddrType_IPv6 && set->McastTxRx);
 
+#if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
     InitializeNetWakeState(m, set);
+#endif
 
     // Scan list to see if this InterfaceID is already represented
     while (*p)
@@ -15843,6 +16796,7 @@
         // In the case of a flapping interface, we pause for five seconds, and reduce the announcement count to one packet.
         mDNSs32 probedelay;
         mDNSu8 numannounce;
+        mdns_clang_ignore_warning_begin(-Wswitch-default);
         switch (activationSpeed)
         {
             case FastActivation:
@@ -15868,6 +16822,7 @@
                 numannounce = InitialAnnounceCount;
                 break;
         }
+        mdns_clang_ignore_warning_end();
 
         LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT, "Interface probe will be delayed - ifname: " PUB_S ", ifaddr: " PRI_IP_ADDR ", probe delay: %d",
             set->ifname, &set->ip, probedelay);
@@ -15900,20 +16855,23 @@
                 m->SuppressProbes = NonZeroTime(m->timenow + probedelay);
         }
 
+    #if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
         // Include OWNER option in packets for 60 seconds after connecting to the network. Setting
         // it here also handles the wake up case as the network link comes UP after waking causing
         // us to reconnect to the network. If we do this as part of the wake up code, it is possible
         // that the network link comes UP after 60 seconds and we never set the OWNER option
         m->AnnounceOwner = NonZeroTime(m->timenow + 60 * mDNSPlatformOneSecond);
         LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEBUG, "Setting AnnounceOwner");
+    #endif
 
         m->mDNSStats.InterfaceUp++;
         for (q = m->Questions; q; q=q->next)                                // Scan our list of questions
         {
             if (mDNSOpaque16IsZero(q->TargetQID))
             {
-                if (!q->InterfaceID || q->InterfaceID == set->InterfaceID)      // If non-specific Q, or Q on this specific interface,
-                {                                                               // then reactivate this question
+                // If the DNSQuestion's mDNS queries are supposed to be sent over the interface, then reactivate it.
+                if (IsInterfaceValidForQuestion(q, set))
+                {
 #if MDNSRESPONDER_SUPPORTS(APPLE, SLOW_ACTIVATION)
                     // If flapping, delay between first and second queries is nine seconds instead of one second
                     mDNSBool dodelay = (activationSpeed == SlowActivation) && (q->FlappingInterface1 == set->InterfaceID || q->FlappingInterface2 == set->InterfaceID);
@@ -15945,7 +16903,7 @@
         // we now need them to re-probe if necessary, and then re-announce.
         for (rr = m->ResourceRecords; rr; rr=rr->next)
         {
-            if (!rr->resrec.InterfaceID || rr->resrec.InterfaceID == set->InterfaceID)
+            if (IsInterfaceValidForAuthRecord(rr, set->InterfaceID))
             {
                 mDNSCoreRestartRegistration(m, rr, numannounce);
             }
@@ -16078,6 +17036,9 @@
             }
 #endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, DISCOVERY_PROXY_CLIENT)
+            DPCHandleInterfaceDown(set->InterfaceID);
+#endif
             // 1. Deactivate any questions specific to this interface, and tag appropriate questions
             // so that mDNS_RegisterInterface() knows how swiftly it needs to reactivate them
             for (q = m->Questions; q; q=q->next)
@@ -16408,6 +17369,13 @@
     mDNS_Unlock(m);
 
     if (err) mDNS_DeregisterService(m, sr);
+#if MDNSRESPONDER_SUPPORTS(APPLE, UNICAST_ASSIST)
+    else if (!RRLocalOnly(&sr->RR_PTR) &&
+             SameDomainName(domain, &localdomain))
+    {
+        unicast_assist_auth_add(sr->RR_PTR.resrec.name, sr->RR_PTR.resrec.namehash, InterfaceID);
+    }
+#endif
     return(err);
 }
 
@@ -16600,6 +17568,13 @@
         for (i=0; i<sr->NumSubTypes; i++)
             mDNS_Deregister_internal(m, &sr->SubTypes[i], drt);
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, UNICAST_ASSIST)
+        if (!RRLocalOnly(&sr->RR_PTR) &&
+            IsLocalDomain(sr->RR_PTR.resrec.name))
+        {
+            unicast_assist_auth_rmv(sr->RR_PTR.resrec.name, sr->RR_PTR.resrec.namehash, sr->RR_PTR.resrec.InterfaceID);
+        }
+#endif
         status = mDNS_Deregister_internal(m, &sr->RR_PTR, drt);
         mDNS_Unlock(m);
         return(status);
@@ -17308,8 +18283,10 @@
     m->SleepState              = SleepState_Awake;
     m->SleepSeqNum             = 0;
     m->SystemWakeOnLANEnabled  = mDNSfalse;
+#if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
     m->SentSleepProxyRegistration = mDNSfalse;
     m->AnnounceOwner           = NonZeroTime(timenow + 60 * mDNSPlatformOneSecond);
+#endif
     m->DelaySleep              = 0;
     m->SleepLimit              = 0;
 
@@ -17423,7 +18400,9 @@
     m->SPSState                 = 0;
     m->SPSProxyListChanged      = mDNSNULL;
     m->SPSSocket                = mDNSNULL;
+#if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
     m->SPSBrowseCallback        = mDNSNULL;
+#endif
     m->ProxyRecords             = 0;
 
     m->DNSPushServers           = mDNSNULL;
@@ -17484,6 +18463,20 @@
     uDNS_SetupDNSConfig(m);                     // Get initial DNS configuration
 #endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, DISCOVERY_PROXY_CLIENT)
+    if (DPCFeatureEnabled())
+    {
+        mDNSPlatformMemZero(&DPCBrowse, sizeof(DPCBrowse));
+        // Note: ConstructServiceName(), which is called by mDNS_StartBrowse_internal(), will turn
+        // "_local._dnssd-dp._tcp" to "_local._sub._dnssd-dp._tcp". See <rdar://3588761>.
+        const domainname *const serviceType = (const domainname *)"\x6" "_local" "\x9" "_dnssd-dp" "\x4" "_tcp";
+        mDNS_StartBrowse_internal(m, &DPCBrowse, serviceType, &localdomain, mDNSInterface_Any, 0, mDNSfalse, mDNSfalse,
+            DPCBrowseHandler, mDNSNULL);
+        // Set querying interval to -1 to avoid scheduling queries for the browse. They'll instead be sent
+        // opportunistically along with other non-probe questions.
+        DPCBrowse.ThisQInterval = -1;
+    }
+#endif
     return(result);
 }
 
@@ -17685,7 +18678,7 @@
         SetDynDNSHostNameIfChanged(m, &fqdn);
         SetConfigState(m, mDNSfalse);
         mDNS_Unlock(m);
-        LogInfo("uDNS_SetupDNSConfig: No configuration change");
+        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT, "uDNS_SetupDNSConfig: No configuration change");
         return mStatus_NoError;
     }
 
@@ -17710,7 +18703,7 @@
     }
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
-    Querier_ProcessDNSServiceChanges();
+    Querier_ProcessDNSServiceChanges(mDNSfalse);
 #else
     // Update our qDNSServer pointers before we go and free the DNSServer object memory
     //
diff --git a/mDNSCore/mDNSDebug.h b/mDNSCore/mDNSDebug.h
index 9d77204..fd86eb1 100644
--- a/mDNSCore/mDNSDebug.h
+++ b/mDNSCore/mDNSDebug.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2002-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
 #include <os/log.h>
+#include "mDNSDebugShared.h"
 #endif
 
 // Set MDNS_DEBUGMSGS to 0 to optimize debugf() calls out of the compiled code
@@ -72,6 +73,7 @@
         extern os_log_t mDNSLogCategory_ ## NAME ## _redacted
 
     MDNS_OS_LOG_CATEGORY_DECLARE_EXTERN(Default);
+    MDNS_OS_LOG_CATEGORY_DECLARE_EXTERN(State);
     MDNS_OS_LOG_CATEGORY_DECLARE_EXTERN(mDNS);
     MDNS_OS_LOG_CATEGORY_DECLARE_EXTERN(uDNS);
     MDNS_OS_LOG_CATEGORY_DECLARE_EXTERN(SPS);
@@ -87,6 +89,7 @@
 #endif
 
 #define MDNS_LOG_CATEGORY_DEFAULT   MDNS_LOG_CATEGORY_DEFINITION(Default)
+#define MDNS_LOG_CATEGORY_STATE     MDNS_LOG_CATEGORY_DEFINITION(State)
 #define MDNS_LOG_CATEGORY_MDNS      MDNS_LOG_CATEGORY_DEFINITION(mDNS)
 #define MDNS_LOG_CATEGORY_UDNS      MDNS_LOG_CATEGORY_DEFINITION(uDNS)
 #define MDNS_LOG_CATEGORY_SPS       MDNS_LOG_CATEGORY_DEFINITION(SPS)
@@ -212,6 +215,7 @@
 #endif
 
 extern int mDNS_LoggingEnabled;
+extern int mDNS_DebugLoggingEnabled;
 extern int mDNS_PacketLoggingEnabled;
 extern int mDNS_McastLoggingEnabled;
 extern int mDNS_McastTracingEnabled;
@@ -304,7 +308,7 @@
     #define LogRedact(CATEGORY, LEVEL, FORMAT, ...)                                         \
         do                                                                                  \
         {                                                                                   \
-            if (!gSensitiveLoggingEnabled)                                               \
+            if (!gSensitiveLoggingEnabled || ((CATEGORY) == (MDNS_LOG_CATEGORY_STATE)))     \
             {                                                                               \
                 os_log_with_type(CATEGORY, LEVEL, FORMAT, ## __VA_ARGS__);                  \
             }                                                                               \
@@ -330,6 +334,84 @@
     #endif
 #endif // MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
 
+//======================================================================================================================
+// MARK: - RData Log Helper
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
+    #define MDNS_CORE_LOG_RDATA_WITH_BUFFER(CATEGORY, LEVEL, RR_PTR, RDATA_BUF, RDATA_BUF_LEN, FORMAT, ...)     \
+        do                                                                                                      \
+        {                                                                                                       \
+            mStatus _get_rdata_err;                                                                             \
+            mDNSu16 _rdataLen;                                                                                  \
+            const mDNSu8 *const _rdataBytes = ResourceRecordGetRDataBytesPointer((RR_PTR), (RDATA_BUF),         \
+                (RDATA_BUF_LEN), &_rdataLen, &_get_rdata_err);                                                  \
+            if (!_get_rdata_err)                                                                                \
+            {                                                                                                   \
+                mDNSu8 *_typeRDataBuf = mDNSNULL;                                                               \
+                mDNSu32 _typeRDataLen = 0;                                                                      \
+                mDNSu8 *_typeRDataBufHeap = mDNSNULL;                                                           \
+                if (sizeof(mDNSStorage.MsgBuffer) >= 2 + _rdataLen)                                             \
+                {                                                                                               \
+                    _typeRDataBuf = (mDNSu8 *)mDNSStorage.MsgBuffer;                                            \
+                    _typeRDataLen = sizeof(mDNSStorage.MsgBuffer);                                              \
+                }                                                                                               \
+                else                                                                                            \
+                {                                                                                               \
+                    _typeRDataLen = 2 + _rdataLen;                                                              \
+                    _typeRDataBufHeap = mDNSPlatformMemAllocate(_typeRDataLen);                                 \
+                    _typeRDataBuf = _typeRDataBufHeap;                                                          \
+                }                                                                                               \
+                LogRedact(CATEGORY, LEVEL,                                                                      \
+                    FORMAT "type: " PUB_DNS_TYPE ", rdata: " PRI_RDATA,                                         \
+                    ##__VA_ARGS__, DNS_TYPE_PARAM((RR_PTR)->rrtype), RDATA_PARAM(_typeRDataBuf, _typeRDataLen,  \
+                    (RR_PTR)->rrtype, _rdataBytes, _rdataLen));                                                 \
+                mDNSPlatformMemFree(_typeRDataBufHeap);                                                         \
+            }                                                                                                   \
+        } while (0)
+#else
+    #define MDNS_CORE_LOG_RDATA_WITH_BUFFER(CATEGORY, LEVEL, RR_PTR, RDATA_BUF, RDATA_BUF_LEN, FORMAT, ...)         \
+        do                                                                                                          \
+        {                                                                                                           \
+            (void)(RDATA_BUF);                                                                                      \
+            (void)(RDATA_BUF_LEN);                                                                                  \
+            LogRedact(CATEGORY, LEVEL, FORMAT " " PRI_S, ##__VA_ARGS__, RRDisplayString(&mDNSStorage, (RR_PTR)));   \
+        } while (0)
+#endif
+
+#define MDNS_CORE_LOG_RDATA(CATEGORY, LEVEL, RR_PTR, FORMAT, ...)                                                   \
+    do                                                                                                              \
+    {                                                                                                               \
+        mDNSu8 *_rdataBuffer = NULL;                                                                                \
+        mDNSu8 *_rdataBufferHeap = NULL;                                                                            \
+        mDNSu16 _rdataBufferLen;                                                                                    \
+        if ((RR_PTR)->rdlength <= sizeof(mDNSStorage.RDataBuffer))                                                  \
+        {                                                                                                           \
+            _rdataBuffer = mDNSStorage.RDataBuffer;                                                                 \
+            _rdataBufferLen = sizeof(mDNSStorage.RDataBuffer);                                                      \
+        }                                                                                                           \
+        else                                                                                                        \
+        {                                                                                                           \
+            _rdataBufferHeap = mDNSPlatformMemAllocate((RR_PTR)->rdlength);                                         \
+            _rdataBuffer = _rdataBufferHeap;                                                                        \
+            _rdataBufferLen = (RR_PTR)->rdlength;                                                                   \
+        }                                                                                                           \
+        if ((RR_PTR)->rdlength == 0)                                                                                \
+        {                                                                                                           \
+            LogRedact(CATEGORY, LEVEL,                                                                              \
+                FORMAT "type: " PUB_DNS_TYPE ", rdata: <none>", ##__VA_ARGS__, DNS_TYPE_PARAM((RR_PTR)->rrtype));   \
+        }                                                                                                           \
+        else                                                                                                        \
+        {                                                                                                           \
+            MDNS_CORE_LOG_RDATA_WITH_BUFFER(CATEGORY, LEVEL, RR_PTR, _rdataBuffer, _rdataBufferLen, FORMAT,         \
+                ##__VA_ARGS__);                                                                                     \
+        }                                                                                                           \
+        mDNSPlatformMemFree(_rdataBufferHeap);                                                                      \
+    }                                                                                                               \
+    while(0)
+
+//======================================================================================================================
+// MARK: - Customized Log Specifier
+
 // The followings are the customized log specifier defined in os_log. For compatibility, we have to define it when it is
 // not on the Apple platform, for example, the Posix platform. The keyword "public" or "private" is used to control whether
 // the content would be redacted when the redaction is turned on: "public" means the content will always be printed;
@@ -353,11 +435,19 @@
 #endif
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
-    #define PUB_BOOL                    "%{BOOL}d"
+    #define PUB_BOOL                    "%{mdns:yesno}d"
     #define BOOL_PARAM(boolean_value)   (boolean_value)
 #else
     #define PUB_BOOL                    PUB_S
-    #define BOOL_PARAM(boolean_value)   ((boolean_value) ? "YES" : "NO")
+    #define BOOL_PARAM(boolean_value)   ((boolean_value) ? "yes" : "no")
+#endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
+    #define PUB_TIMEV                   "%{public, timeval}.*P"
+    #define TIMEV_PARAM(time_val_ptr)   ((int)sizeof(*time_val_ptr)), time_val_ptr
+#else
+    #define PUB_TIMEV                   "%ld"
+    #define TIMEV_PARAM(time_val_ptr)   ((time_val_ptr)->tv_sec)
 #endif
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
@@ -439,24 +529,28 @@
 #endif
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
-    #define PUB_DM_NAME "%{public, mdnsresponder:domain_name}.*P"
-    #define PRI_DM_NAME "%{sensitive, mask.hash, mdnsresponder:domain_name}.*P"
+    #define PUB_DM_NAME                 "%{public, mdnsresponder:domain_name}.*P"
+    #define PRI_DM_NAME                 "%{sensitive, mask.hash, mdnsresponder:domain_name}.*P"
     // When DM_NAME_PARAM is used, the file where the function is defined must include DNSEmbeddedAPI.h
-    #define DM_NAME_PARAM(name) ((name) ? ((int)DomainNameLength((name))) : 0), (name)
+    #define DM_NAME_PARAM(name)         ((name) ? ((int)DomainNameLength((name))) : 0), (name)
+    #define DM_NAME_PARAM_NONNULL(name) (int)DomainNameLength(name), (name)
 #else
-    #define PUB_DM_NAME "%##s"
-    #define PRI_DM_NAME PUB_DM_NAME
-    #define DM_NAME_PARAM(name) (name)
+    #define PUB_DM_NAME                 "%##s"
+    #define PRI_DM_NAME                 PUB_DM_NAME
+    #define DM_NAME_PARAM(name)         (name)
+    #define DM_NAME_PARAM_NONNULL(name) (name)
 #endif
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
-    #define PUB_DM_LABEL "%{public, mdnsresponder:domain_label}.*P"
-    #define PRI_DM_LABEL "%{sensitive, mask.hash, mdnsresponder:domain_label}.*P"
-    #define DM_LABEL_PARAM(label) 1 + ((label)->c[0]), ((label)->c)
+    #define PUB_DM_LABEL                "%{public, mdnsresponder:domain_label}.*P"
+    #define PRI_DM_LABEL                "%{sensitive, mask.hash, mdnsresponder:domain_label}.*P"
+    #define DM_LABEL_PARAM(label)       1 + ((label)->c[0]), ((label)->c)
+    #define DM_LABEL_PARAM_SAFE(label)  (label ? 1 + ((label)->c[0]) : 0), ((label)->c)
 #else
-    #define PUB_DM_LABEL "%#s"
-    #define PRI_DM_LABEL PUB_DM_LABEL
-    #define DM_LABEL_PARAM(label) (label)
+    #define PUB_DM_LABEL                "%#s"
+    #define PRI_DM_LABEL                PUB_DM_LABEL
+    #define DM_LABEL_PARAM(label)       (label)
+    #define DM_LABEL_PARAM_SAFE(label)  (label)
 #endif
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
@@ -583,6 +677,50 @@
         #define DNSSEC_INVAL_STATE_PARAM(state_value)   ("<DNSSEC Unsupported>")
 #endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
+    #define PUB_TIME_DUR    "%{mdns:time_duration}u"
+#else
+    #define PUB_TIME_DUR    "%us"
+#endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
+    #define PUB_OS_ERR    "%{mdns:err}ld"
+#else
+    #define PUB_OS_ERR    "%ld"
+#endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
+    #define PUB_RDATA       "%{public, mdns:rdata}.*P"
+    #define PRI_RDATA       "%{sensitive, mask.hash, mdns:rdata}.*P"
+    #define RDATA_PARAM(buf, buf_len, rrtype, rdata, rdata_len) \
+                            (rdata_len + 2), GetPrintableRDataBytes(buf, buf_len, rrtype, rdata, rdata_len)
+
+    #define PUB_TYPE_RDATA  "%{public, mdns:rrtype+rdata}.*P"
+    #define PRI_TYPE_RDATA  "%{sensitive, mask.hash, mdns:rrtype+rdata}.*P"
+    #define TYPE_RDATA_PARAM(buf, buf_len, rrtype, rdata, rdata_len) RDATA_PARAM(buf, buf_len, rrtype, rdata, rdata_len)
+#else
+    #define PUB_RDATA       "%p"
+    #define PRI_RDATA       PUB_RDATA
+    #define RDATA_PARAM(buf, buf_len, rrtype, rdata, rdata_len) (rdata)
+
+    #define PUB_TYPE_RDATA  PUB_S " %p"
+    #define PRI_TYPE_RDATA  PUB_TYPE_RDATA
+    #define TYPE_RDATA_PARAM(buf, buf_len, rrtype, rdata, rdata_len) \
+                            DNSTypeName(rrtype), RDATA_PARAM(buf, buf_len, rrtype, rdata, rdata_len)
+#endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
+    #define PUB_D2D_SRV_EVENT   "%{public, mdnsresponder:d2d_service_event}d"
+#endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
+    #define PUB_DNS_SCOPE_TYPE          "%{public, mdnsresponder:dns_scope_type}d"
+    #define DNS_SCOPE_TYPE_PARAM(type)  (type)
+#else
+    #define PUB_DNS_SCOPE_TYPE          "%s"
+    #define DNS_SCOPE_TYPE_PARAM(type)  DNSScopeToString(type)
+#endif
+
 extern void LogToFD(int fd, const char *format, ...);
 
 #endif // __mDNSDebug_h
diff --git a/mDNSCore/mDNSEmbeddedAPI.h b/mDNSCore/mDNSEmbeddedAPI.h
index 696b30e..3b36688 100644
--- a/mDNSCore/mDNSEmbeddedAPI.h
+++ b/mDNSCore/mDNSEmbeddedAPI.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2002-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -192,8 +192,8 @@
 
 #ifndef fallthrough
  #if MDNS_COMPILER_IS_CLANG()
-  #if __has_c_attribute(fallthrough)
-   #define fallthrough() [[fallthrough]]
+  #if __has_attribute(fallthrough)
+   #define fallthrough() __attribute__((fallthrough))
   #else
    #define fallthrough()
   #endif
@@ -438,7 +438,9 @@
     mStatus_Timeout                   = -65568,
     mStatus_DefunctConnection         = -65569,
     mStatus_PolicyDenied              = -65570,
-    // -65571 to -65785 currently unused; available for allocation
+    mStatus_NotPermitted              = -65571,     // From kDNSSDAdvertisingProxyStatus_NotPermitted
+    mStatus_StaleData                 = -65572,
+    // -65573 to -65785 currently unused; available for allocation
 
     // udp connection status
     mStatus_HostUnreachErr    = -65786,
@@ -499,11 +501,12 @@
 
 // Most records have a TTL of 75 minutes, so that their 80% cache-renewal query occurs once per hour.
 // For records containing a hostname (in the name on the left, or in the rdata on the right),
-// like A, AAAA, reverse-mapping PTR, and SRV, we use a two-minute TTL by default, because we don't want
-// them to hang around for too long in the cache if the host in question crashes or otherwise goes away.
+// like A, AAAA, reverse-mapping PTR, and SRV, we previously used a two-minute TTL by default, because we did't want
+// them to hang around for too long in the cache if the host in question crashes or otherwise goes away... but to reduce
+// the multicast traffic required to refresh these records, the same 75 minute TTL is now used for all record types.
 
 #define kStandardTTL (3600UL * 100 / 80)
-#define kHostNameTTL 120UL
+#define kHostNameTTL kStandardTTL           // Was 120UL
 
 // Multicast DNS uses announcements (gratuitous responses) to update peer caches.
 // This means it is feasible to use relatively larger TTL values than we might otherwise
@@ -866,6 +869,7 @@
 #define kDNSOpt_NSID  3
 #define kDNSOpt_Owner 4
 #define kDNSOpt_Trace 65001  // 65001-65534 Reserved for Local/Experimental Use
+#define kDNSOpt_TSR   65002
 
 typedef struct
 {
@@ -892,12 +896,19 @@
     mDNSu32   mDNSv;      // mDNSResponder Version (DNS_SD_H defined in dns_sd.h)
 } TracerOptData;
 
+typedef struct
+{
+    mDNSs32 timeStamp;      // TSR record timestamp
+    mDNSu32 hostkeyHash;    // 32-bit Hostkey Hash value
+    mDNSu16 recIndex;       // Index into the DNS packet of the first answer (1-based)
+} TSROptData;
+
 // Note: rdataOPT format may be repeated an arbitrary number of times in a single resource record
 typedef struct
 {
     mDNSu16 opt;
     mDNSu16 optlen;
-    union { LLQOptData llq; mDNSu32 updatelease; OwnerOptData owner; TracerOptData tracer; } u;
+    union { LLQOptData llq; mDNSu32 updatelease; OwnerOptData owner; TracerOptData tracer; TSROptData tsr; } u;
 } rdataOPT;
 
 // Space needed to put OPT records into a packet:
@@ -906,7 +917,7 @@
 // Lease rdata     8  bytes (opt 2, len 2, lease 4)
 // Owner rdata 12-24  bytes (opt 2, len 2, owner 8-20)
 // Trace rdata     9  bytes (opt 2, len 2, platf 1, mDNSv 4)
-
+// TSR rdata      14  bytes (opt 2, len 2, time 4, hash 4, index 2)
 
 #define DNSOpt_Header_Space                 11
 #define DNSOpt_LLQData_Space               (4 + 2 + 2 + 2 + 8 + 4)
@@ -916,6 +927,7 @@
 #define DNSOpt_OwnerData_ID_Wake_PW4_Space (4 + 2 + 6 + 6 + 4)
 #define DNSOpt_OwnerData_ID_Wake_PW6_Space (4 + 2 + 6 + 6 + 6)
 #define DNSOpt_TraceData_Space             (4 + 1 + 4)
+#define DNSOpt_TSRData_Space               (4 + 4 + 4 + 2)
 
 #define ValidOwnerLength(X) (   (X) == DNSOpt_OwnerData_ID_Space          - 4 || \
                                 (X) == DNSOpt_OwnerData_ID_Wake_Space     - 4 || \
@@ -928,6 +940,7 @@
         (O)->opt == kDNSOpt_LLQ   ? DNSOpt_LLQData_Space   :        \
         (O)->opt == kDNSOpt_Lease ? DNSOpt_LeaseData_Space :        \
         (O)->opt == kDNSOpt_Trace ? DNSOpt_TraceData_Space :        \
+        (O)->opt == kDNSOpt_TSR   ? DNSOpt_TSRData_Space   :        \
         (O)->opt == kDNSOpt_Owner ? DNSOpt_Owner_Space(&(O)->u.owner.HMAC, &(O)->u.owner.IMAC) : 0x10000)
 
 // NSEC record is defined in RFC 4034.
@@ -1494,7 +1507,6 @@
     mDNSInterfaceID LastMCInterface;    // Interface this record was multicast on at the time LastMCTime was recorded
     RData          *NewRData;           // Set if we are updating this record with new rdata
     mDNSu16 newrdlength;                // ... and the length of the new RData
-    mDNSBool Tentative;                 // Set if there is more recent TSR value is in probe, records with Tentative set to true will be deregistered when announcement received and conflict happen
     mDNSRecordUpdateCallback *UpdateCallback;
     mDNSu32 UpdateCredits;              // Token-bucket rate limiting of excessive updates
     mDNSs32 NextUpdateCredit;           // Time next token is added to bucket
@@ -1612,7 +1624,7 @@
     mDNSs32 LastUnansweredTime;         // In platform time units; last time we incremented UnansweredQueries
     mDNSu8  UnansweredQueries;          // Number of times we've issued a query for this record without getting an answer
 
-#if MDNSRESPONDER_SUPPORTS(COMMON, DNS_PUSH)
+#if MDNSRESPONDER_SUPPORTS(COMMON, DNS_PUSH) || MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
     mDNSBool DNSPushSubscribed;         // Indicate whether the cached record has an active DNS push subscription. If
                                         // true, the record never expires.
 #endif
@@ -1976,6 +1988,9 @@
 #if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
     dns_push_obj_dns_question_member_t dns_push;
 #endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, DISCOVERY_PROXY_CLIENT)
+    CFMutableSetRef DPSubscribers;          // Current set of local domain Discovery Proxy subscribers.
+#endif
     mDNSu32 qnamehash;
     mDNSs32 DelayAnswering;                 // Set if we want to defer answering this question until the cache settles
     mDNSs32 LastQTime;                      // Last scheduled transmission of this Q on *all* applicable interfaces
@@ -2062,6 +2077,7 @@
     mDNSBool NeedUpdatedQuerier;            // True if new querier is needed for DNSQuestion's updated qname/qtype/qclass.
     mDNSBool UsedAsFailFastProbe;           // True if used as a probe for fail-fast service with connection problems.
     mDNSBool ProhibitEncryptedDNS;          // True if use of encrypted DNS protocols is prohibited.
+    mDNSBool OverrideDNSService;            // True if resolver UUID overrides normal DNS service selection.
 #endif
     mDNSu8 ProxyQuestion;                   // Proxy Question
 #if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
@@ -2080,15 +2096,18 @@
     mDNSu8 unansweredQueries;               // The number of unanswered queries to this server
     mDNSBool Restart;                       // This question should be restarted soon.
 #endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, UNICAST_ASSIST)
+    mDNSBool initialAssistPerformed;        // Initial quetion unicast assist logic was performed
+#endif
 #if MDNSRESPONDER_SUPPORTS(APPLE, LOG_PRIVACY_LEVEL)
     dnssd_log_privacy_level_t logPrivacyLevel; // The log privacy level that the client wishes to have when the question
                                                // is started.
 #endif
 #if MDNSRESPONDER_SUPPORTS(APPLE, PADDING_CHECKS)
     #if TARGET_OS_OSX || TARGET_OS_TV
-        MDNS_STRUCT_PAD(4);
+        MDNS_STRUCT_PAD(2);
     #else
-        MDNS_STRUCT_PAD_64_32(0, 0);
+        MDNS_STRUCT_PAD_64_32(6, 2);
     #endif
 #endif
 };
@@ -2161,12 +2180,14 @@
     mDNSu8 IPv4Available;               // If InterfaceActive, set if v4 available on this InterfaceID
     mDNSu8 IPv6Available;               // If InterfaceActive, set if v6 available on this InterfaceID
 
+#if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
     DNSQuestion NetWakeBrowse;
     DNSQuestion NetWakeResolve[3];      // For fault-tolerance, we try up to three Sleep Proxies
     mDNSAddr SPSAddr[3];
     mDNSIPPort SPSPort[3];
     mDNSs32 NextSPSAttempt;             // -1 if we're not currently attempting to register with any Sleep Proxy
     mDNSs32 NextSPSAttemptTime;
+#endif
 
     // Standard AuthRecords that every Responder host should have (one per active IP address)
     AuthRecord RR_A;                    // 'A' or 'AAAA' (address) record for our ".local" name
@@ -2374,9 +2395,13 @@
     mDNSu8 SleepState;                  // Set if we're sleeping
     mDNSu8 SleepSeqNum;                 // "Epoch number" of our current period of wakefulness
     mDNSu8 SystemWakeOnLANEnabled;      // Set if we want to register with a Sleep Proxy before going to sleep
+#if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
     mDNSu8 SentSleepProxyRegistration;  // Set if we registered (or tried to register) with a Sleep Proxy
+#endif
     mDNSu8 SystemSleepOnlyIfWakeOnLAN;  // Set if we may only sleep if we managed to register with a Sleep Proxy
+#if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
     mDNSs32 AnnounceOwner;              // After waking from sleep, include OWNER option in packets until this time
+#endif
     mDNSs32 DelaySleep;                 // To inhibit re-sleeping too quickly right after wake
     mDNSs32 SleepLimit;                 // Time window to allow deregistrations, etc.,
                                         // during which underying platform layer should inhibit system sleep
@@ -2524,7 +2549,9 @@
 #ifndef SPC_DISABLED
     ServiceRecordSet SPSRecords;
 #endif
+#if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
     mDNSQuestionCallback *SPSBrowseCallback;    // So the platform layer can do something useful with SPS browse results
+#endif
     int ProxyRecords;                           // Total number of records we're holding as proxy
     #define           MAX_PROXY_RECORDS 10000   /* DOS protection: 400 machines at 25 records each */
 
@@ -2556,13 +2583,17 @@
 #ifndef MaxMsg
     #define MaxMsg 512
 #endif
+    mDNSu8 RDataBuffer[MaxMsg];             // Temp storage used to construct rrtype + rdata bytes for logging.
     char MsgBuffer[MaxMsg];                 // Temp storage used while building error log messages (keep at end of struct)
 };
 
-#define FORALL_CACHERECORDS(SLOT,CG,CR)                           \
+#define FORALL_CACHEGROUPS(SLOT,CG)                               \
     for ((SLOT) = 0; (SLOT) < CACHE_HASH_SLOTS; (SLOT)++)         \
-        for ((CG)=m->rrcache_hash[(SLOT)]; (CG); (CG)=(CG)->next) \
-            for ((CR) = (CG)->members; (CR); (CR)=(CR)->next)
+        for ((CG)=m->rrcache_hash[(SLOT)]; (CG); (CG)=(CG)->next)
+
+#define FORALL_CACHERECORDS(SLOT,CG,CR)                           \
+    FORALL_CACHEGROUPS(SLOT,CG)                                   \
+        for ((CR) = (CG)->members; (CR); (CR)=(CR)->next)
 
 // ***************************************************************************
 #if 0
@@ -2633,7 +2664,8 @@
 #define SleepProxyServiceType (*(const domainname *)"\xC" "_sleep-proxy" "\x4" "_udp")
 
 #if MDNSRESPONDER_SUPPORTS(COMMON, LOCAL_DNS_RESOLVER_DISCOVERY)
-    #define THREAD_DOMAIN_NAME ((const domainname *) "\xA" "openthread" "\x6" "thread" "\x4" "home" "\x4" "arpa")
+    // Change `Do53_UNICAST_DISCOVERY_DOMAIN` to a non-root domain to do Do53 service discovery under this domain.
+    #define Do53_UNICAST_DISCOVERY_DOMAIN ((const domainname *) "")
 #endif // MDNSRESPONDER_SUPPORTS(COMMON, LOCAL_DNS_RESOLVER_DISCOVERY)
 
 // ***************************************************************************
@@ -2813,7 +2845,8 @@
 // mDNS_Dereg_rapid is used to send one goodbye instead of three, when we want the memory available for reuse sooner
 // mDNS_Dereg_conflict is used to indicate that this record is being forcibly deregistered because of a conflict
 // mDNS_Dereg_repeat is used when cleaning up, for records that may have already been forcibly deregistered
-typedef enum { mDNS_Dereg_normal, mDNS_Dereg_rapid, mDNS_Dereg_conflict, mDNS_Dereg_repeat } mDNS_Dereg_type;
+// mDNS_Dereg_stale is used when the registered record has been superseded by another host
+typedef enum { mDNS_Dereg_normal, mDNS_Dereg_rapid, mDNS_Dereg_conflict, mDNS_Dereg_repeat, mDNS_Dereg_stale } mDNS_Dereg_type;
 
 // mDNS_RegisterService is a single call to register the set of resource records associated with a given named service.
 //
@@ -3016,6 +3049,10 @@
 #define RRDisplayString(m, rr) GetRRDisplayString_rdb(rr, &(rr)->rdata->u, (m)->MsgBuffer)
 #define ARDisplayString(m, rr) GetRRDisplayString_rdb(&(rr)->resrec, &(rr)->resrec.rdata->u, (m)->MsgBuffer)
 #define CRDisplayString(m, rr) GetRRDisplayString_rdb(&(rr)->resrec, &(rr)->resrec.rdata->u, (m)->MsgBuffer)
+#if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
+extern const mDNSu8 *GetPrintableRDataBytes(mDNSu8 *outBuffer, mDNSu32 bufferLen, mDNSu16 recordType,
+    const mDNSu8 *rdata, mDNSu32 rdataLen);
+#endif
 #define MortalityDisplayString(M) (M == Mortality_Mortal ? "mortal" : (M == Mortality_Immortal ? "immortal" : "ghost"))
 extern mDNSBool mDNSSameAddress(const mDNSAddr *ip1, const mDNSAddr *ip2);
 extern void IncrementLabelSuffix(domainlabel *name, mDNSBool RichText);
@@ -3246,6 +3283,16 @@
 extern void     mDNSPlatformMemFree(void *mem);
 #endif // MDNS_MALLOC_DEBUGGING
 
+#define mDNSPlatformMemForget(PTR)          \
+    do                                      \
+    {                                       \
+        if (*(PTR))                         \
+        {                                   \
+            mDNSPlatformMemFree(*(PTR));    \
+            *(PTR) = NULL;                  \
+        }                                   \
+    } while(0)
+
 // If the platform doesn't have a strong PRNG, we define a naive multiply-and-add based on a seed
 // from the platform layer.  Long-term, we should embed an arc4 implementation, but the strength
 // will still depend on the randomness of the seed.
@@ -3379,7 +3426,7 @@
 #endif
 extern mDNSBool   mDNSPlatformValidRecordForQuestion(const ResourceRecord *const rr, const DNSQuestion *const q);
 extern mDNSBool   mDNSPlatformValidRecordForInterface(const AuthRecord *rr, mDNSInterfaceID InterfaceID);
-extern mDNSBool   mDNSPlatformValidQuestionForInterface(DNSQuestion *q, const NetworkInterfaceInfo *intf);
+extern mDNSBool   mDNSPlatformValidQuestionForInterface(const DNSQuestion *q, const NetworkInterfaceInfo *intf);
 
 extern void mDNSPlatformFormatTime(unsigned long t, mDNSu8 *buf, int bufsize);
 
@@ -3486,7 +3533,7 @@
 
 typedef mDNSu32 CreateNewCacheEntryFlags;
 #define kCreateNewCacheEntryFlagsNone 0
-#if MDNSRESPONDER_SUPPORTS(COMMON, DNS_PUSH)
+#if MDNSRESPONDER_SUPPORTS(COMMON, DNS_PUSH) || MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
 #define kCreateNewCacheEntryFlagsDNSPushSubscribed (1U << 0)
 #endif
 #if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
@@ -3510,10 +3557,17 @@
 extern void CompleteDeregistration(mDNS *const m, AuthRecord *rr);
 extern void AnswerCurrentQuestionWithResourceRecord(mDNS *const m, CacheRecord *const rr, const QC_result AddRecord);
 extern void AnswerQuestionByFollowingCNAME(mDNS *const m, DNSQuestion *q, ResourceRecord *rr);
+extern NetworkInterfaceInfo *FirstInterfaceForID(mDNS *const m, const mDNSInterfaceID InterfaceID);
+extern NetworkInterfaceInfo *FirstIPv4LLInterfaceForID(mDNS *const m, const mDNSInterfaceID InterfaceID);
 extern char *InterfaceNameForID(mDNS *const m, const mDNSInterfaceID InterfaceID);
+extern const char *InterfaceNameForIDOrEmptyString(mDNSInterfaceID InterfaceID);
 extern void CacheRecordSetResponseFlags(CacheRecord *const cr, const mDNSOpaque16 responseFlags);
 extern void mDNSCoreResetRecord(mDNS *const m);
-extern AuthRecord *mDNSGetTSRRecord(mDNS *m, const AuthRecord *rr);
+extern mDNSBool getValidContinousTSRTime(mDNSs32 *timestampContinuous, mDNSu32 tsrTimestamp);
+extern AuthRecord *mDNSGetTSRForAuthRecord(mDNS *m, const AuthRecord *rr);
+extern CacheRecord *mDNSGetTSRForCacheGroup(const CacheGroup *const cg);
+typedef enum { eTSRCheckLose = -1, eTSRCheckNoKeyMatch = 0, eTSRCheckKeyMatch, eTSRCheckWin } eTSRCheckResult;
+extern eTSRCheckResult CheckTSRForResourceRecord(const TSROptData *curTSROpt, const ResourceRecord *ourTSRRec);
 #if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
 extern void DNSServerChangeForQuestion(mDNS *const m, DNSQuestion *q, DNSServer *newServer);
 #endif
@@ -3699,7 +3753,9 @@
 #define mDNSCoreBeSleepProxyServer(M,S,P,MP,TP,F)                       \
     do { mDNS_Lock(m); mDNSCoreBeSleepProxyServer_internal((M),(S),(P),(MP),(TP),(F)); mDNS_Unlock(m); } while(0)
 
+#if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
 extern void FindSPSInCache(mDNS *const m, const DNSQuestion *const q, const CacheRecord *sps[3]);
+#endif
 #define PrototypeSPSName(X) ((X)[0] >= 11 && (X)[3] == '-' && (X)[ 4] == '9' && (X)[ 5] == '9' && \
                              (X)[6] == '-' && (X)[ 7] == '9' && (X)[ 8] == '9' && \
                              (X)[9] == '-' && (X)[10] == '9' && (X)[11] == '9'    )
@@ -3777,14 +3833,14 @@
     char sizecheck_AuthRecord          [(sizeof(AuthRecord)           <=  1176) ? 1 : -1];
     char sizecheck_CacheRecord         [(sizeof(CacheRecord)          <=   224) ? 1 : -1];
     char sizecheck_CacheGroup          [(sizeof(CacheGroup)           <=   224) ? 1 : -1];
-    char sizecheck_DNSQuestion         [(sizeof(DNSQuestion)          <=   696) ? 1 : -1];
-    char sizecheck_ZoneData            [(sizeof(ZoneData)             <=  1528) ? 1 : -1];
+    char sizecheck_DNSQuestion         [(sizeof(DNSQuestion)          <=   712) ? 1 : -1];
+    char sizecheck_ZoneData            [(sizeof(ZoneData)             <=  1544) ? 1 : -1];
     char sizecheck_NATTraversalInfo    [(sizeof(NATTraversalInfo)     <=   200) ? 1 : -1];
     char sizecheck_HostnameInfo        [(sizeof(HostnameInfo)         <=  3050) ? 1 : -1];
 #if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     char sizecheck_DNSServer           [(sizeof(DNSServer)            <=   328) ? 1 : -1];
 #endif
-    char sizecheck_NetworkInterfaceInfo[(sizeof(NetworkInterfaceInfo) <=  6544) ? 1 : -1];
+    char sizecheck_NetworkInterfaceInfo[(sizeof(NetworkInterfaceInfo) <=  6576) ? 1 : -1];
     char sizecheck_ServiceRecordSet    [(sizeof(ServiceRecordSet)     <=  4792) ? 1 : -1];
     char sizecheck_DomainAuthInfo      [(sizeof(DomainAuthInfo)       <=   944) ? 1 : -1];
 #if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
diff --git a/mDNSCore/uDNS.c b/mDNSCore/uDNS.c
index e06d27f..a67fdc5 100644
--- a/mDNSCore/uDNS.c
+++ b/mDNSCore/uDNS.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2002-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -1809,7 +1809,7 @@
 mDNSlocal mStatus GetZoneData_StartQuery(mDNS *const m, ZoneData *zd, mDNSu16 qtype);
 
 // GetZoneData_QuestionCallback is called from normal client callback context (core API calls allowed)
-mDNSlocal void GetZoneData_QuestionCallback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord)
+mDNSexport void GetZoneData_QuestionCallback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord)
 {
     ZoneData *zd = (ZoneData*)question->QuestionContext;
 
@@ -1817,6 +1817,9 @@
 
     if (!AddRecord) return;                                             // Don't care about REMOVE events
     if (AddRecord == QC_addnocache && answer->rdlength == 0) return;    // Don't care about transient failure indications
+    if (AddRecord == QC_suppressed && answer->rdlength == 0) return;    // Ignore the suppression result caused by no
+                                                                        // DNS service, in which case we should not move
+                                                                        // to the next name labels.
     if (answer->rrtype != question->qtype) return;                      // Don't care about CNAMEs
 
     if (answer->rrtype == kDNSType_SOA)
@@ -2387,6 +2390,7 @@
         return;
     case regState_Unregistered:
     case regState_Zero:
+    MDNS_COVERED_SWITCH_DEFAULT:
         break;
     }
     LogMsg("UpdateOneSRVRecord: Unknown state %d for %##s", rr->state, rr->resrec.name->c);
@@ -4339,6 +4343,7 @@
     case regState_UpdatePending:
     case regState_Registered: break;
     case regState_DeregPending: break;
+    MDNS_COVERED_SWITCH_DEFAULT: break;
 
     case regState_NATError:
     case regState_NATMap:
@@ -4504,6 +4509,9 @@
     case regState_NATError:
         LogMsg("ERROR: uDNS_UpdateRecord called for record %##s with bad state regState_NATError", rr->resrec.name->c);
         return mStatus_UnknownErr;      // states for service records only
+
+    MDNS_COVERED_SWITCH_DEFAULT:
+        break;
     }
     LogMsg("uDNS_UpdateRecord: Unknown state %d for %##s", rr->state, rr->resrec.name->c);
 
@@ -4516,6 +4524,8 @@
 // ***************************************************************************
 // MARK: - Periodic Execution Routines
 
+#if !MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
+
 mDNSlocal const char *LLQStateToString(LLQ_State state);
 
 mDNSlocal void uDNS_HandleLLQState(mDNS *const NONNULL m, DNSQuestion *const NONNULL q)
@@ -4665,6 +4675,7 @@
         DNSTypeName(q->qtype));
 #endif
 }
+#endif // !MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
 
 // The question to be checked is not passed in as an explicit parameter;
 // instead it is implicit that the question to be checked is m->CurrentQuestion.
@@ -4673,6 +4684,7 @@
     DNSQuestion *q = m->CurrentQuestion;
     if (m->timenow - NextQSendTime(q) < 0) return;
 
+#if !MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
     if (q->LongLived)
     {
         uDNS_HandleLLQState(m,q);
@@ -4693,6 +4705,7 @@
         }
 #endif // MDNSRESPONDER_SUPPORTS(COMMON, DNS_PUSH)
     }
+#endif // !MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     Querier_HandleUnicastQuestion(q);
@@ -5250,7 +5263,9 @@
         {
             // If domain is already in list, and marked for deletion, unmark the delete
             // Be careful not to touch the other flags that may be present
-            LogInfo("mDNS_AddSearchDomain already in list %##s", domain->c);
+            LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
+                "mDNS_AddSearchDomain: domain already in list -- search domain: " PRI_DM_NAME,
+                DM_NAME_PARAM_NONNULL(domain));
             if ((*p)->flag & SLE_DELETE) (*p)->flag &= ~SLE_DELETE;
             tmp = *p;
             *p = tmp->next;
@@ -5267,11 +5282,17 @@
     {
         // if domain not in list, add to list, mark as add (1)
         *p = (SearchListElem *) mDNSPlatformMemAllocateClear(sizeof(**p));
-        if (!*p) { LogMsg("ERROR: mDNS_AddSearchDomain - malloc"); return; }
+        if (!*p)
+        {
+            LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_ERROR, "ERROR: mDNS_AddSearchDomain - malloc");
+            return;
+        }
         AssignDomainName(&(*p)->domain, domain);
         (*p)->next = mDNSNULL;
         (*p)->InterfaceID = InterfaceID;
-        LogInfo("mDNS_AddSearchDomain created new %##s, InterfaceID %p", domain->c, InterfaceID);
+        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
+            "mDNS_AddSearchDomain: new search domain added -- search domain: " PRI_DM_NAME ", InterfaceID %p",
+            DM_NAME_PARAM_NONNULL(domain), InterfaceID);
     }
 }
 
@@ -5424,7 +5445,7 @@
         // before.
         for (ptr = SearchList; ptr; ptr = ptr->next)
             ptr->flag &= ~SLE_DELETE;
-        LogInfo("uDNS_SetupWABQueries: No config change");
+        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT, "uDNS_SetupWABQueries: No config change");
     }
     mDNS_Unlock(m);
 
@@ -5440,7 +5461,10 @@
     while (*p)
     {
         ptr = *p;
-        LogInfo("uDNS_SetupWABQueries:action 0x%x: Flags 0x%x,  AuthRecs %p, InterfaceID %p %##s", action, ptr->flag, ptr->AuthRecs, ptr->InterfaceID, ptr->domain.c);
+        const mDNSu32 nameHash = mDNS_DomainNameFNV1aHash(&ptr->domain);
+        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
+            "uDNS_SetupWABQueries -- action: 0x%x, flags: 0x%x, ifid: %p, domain: " PUB_DM_NAME " (%x)",
+            action, ptr->flag, ptr->InterfaceID, DM_NAME_PARAM(&ptr->domain), nameHash);
         // If SLE_DELETE is set, stop all the queries, deregister all the records and free the memory.
         // Otherwise, check to see what the "action" requires. If a particular action bit is not set and
         // we have started the corresponding queries as indicated by the "flags", stop those queries and
@@ -5462,20 +5486,23 @@
                 if ((ptr->flag & SLE_WAB_BROWSE_QUERY_STARTED) &&
                     !SameDomainName(&ptr->domain, &localdomain) && (ptr->InterfaceID == mDNSInterface_Any))
                 {
-                    LogInfo("uDNS_SetupWABQueries: DELETE  Browse for domain  %##s", ptr->domain.c);
+                    LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
+                        "uDNS_SetupWABQueries: DELETE Browse for domain -- name hash: %x", nameHash);
                     mDNS_StopGetDomains(m, &ptr->BrowseQ);
                     mDNS_StopGetDomains(m, &ptr->DefBrowseQ);
                 }
                 if ((ptr->flag & SLE_WAB_LBROWSE_QUERY_STARTED) &&
                     !SameDomainName(&ptr->domain, &localdomain) && (ptr->InterfaceID == mDNSInterface_Any))
                 {
-                    LogInfo("uDNS_SetupWABQueries: DELETE  Legacy Browse for domain  %##s", ptr->domain.c);
+                    LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
+                        "uDNS_SetupWABQueries: DELETE Legacy Browse for domain -- name hash: %x", nameHash);
                     mDNS_StopGetDomains(m, &ptr->AutomaticBrowseQ);
                 }
                 if ((ptr->flag & SLE_WAB_REG_QUERY_STARTED) &&
                     !SameDomainName(&ptr->domain, &localdomain) && (ptr->InterfaceID == mDNSInterface_Any))
                 {
-                    LogInfo("uDNS_SetupWABQueries: DELETE  Registration for domain  %##s", ptr->domain.c);
+                    LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
+                        "uDNS_SetupWABQueries: DELETE Registration for domain -- name hash: %x", nameHash);
                     mDNS_StopGetDomains(m, &ptr->RegisterQ);
                     mDNS_StopGetDomains(m, &ptr->DefRegisterQ);
                 }
@@ -5487,9 +5514,16 @@
                 {
                     ARListElem *dereg = arList;
                     arList = arList->next;
-                    LogInfo("uDNS_SetupWABQueries: DELETE Deregistering PTR %##s -> %##s", dereg->ar.resrec.name->c, dereg->ar.resrec.rdata->u.name.c);
+                    LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
+                        "uDNS_SetupWABQueries: DELETE Deregistering PTR -- "
+                        "record: " PRI_DM_NAME " PTR " PRI_DM_NAME, DM_NAME_PARAM(dereg->ar.resrec.name),
+                        DM_NAME_PARAM(&dereg->ar.resrec.rdata->u.name));
                     err = mDNS_Deregister(m, &dereg->ar);
-                    if (err) LogMsg("uDNS_SetupWABQueries:: ERROR!! mDNS_Deregister returned %d", err);
+                    if (err)
+                    {
+                        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_ERROR,
+                            "uDNS_SetupWABQueries: mDNS_Deregister returned error -- error: %d", err);
+                    }
                     // Memory will be freed in the FreeARElemCallback
                 }
                 continue;
@@ -5501,7 +5535,8 @@
             if (!(action & UDNS_WAB_BROWSE_QUERY) && (ptr->flag & SLE_WAB_BROWSE_QUERY_STARTED) &&
                 !SameDomainName(&ptr->domain, &localdomain) && (ptr->InterfaceID == mDNSInterface_Any))
             {
-                LogInfo("uDNS_SetupWABQueries: Deleting Browse for domain  %##s", ptr->domain.c);
+                LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
+                    "uDNS_SetupWABQueries: Deleting Browse for domain -- name hash: %x", nameHash);
                 ptr->flag &= ~SLE_WAB_BROWSE_QUERY_STARTED;
                 uDNS_DeleteWABQueries(m, ptr, UDNS_WAB_BROWSE_QUERY);
             }
@@ -5509,7 +5544,8 @@
             if (!(action & UDNS_WAB_LBROWSE_QUERY) && (ptr->flag & SLE_WAB_LBROWSE_QUERY_STARTED) &&
                 !SameDomainName(&ptr->domain, &localdomain) && (ptr->InterfaceID == mDNSInterface_Any))
             {
-                LogInfo("uDNS_SetupWABQueries: Deleting Legacy Browse for domain  %##s", ptr->domain.c);
+                LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
+                    "uDNS_SetupWABQueries: Deleting Legacy Browse for domain -- name hash: %x", nameHash);
                 ptr->flag &= ~SLE_WAB_LBROWSE_QUERY_STARTED;
                 uDNS_DeleteWABQueries(m, ptr, UDNS_WAB_LBROWSE_QUERY);
             }
@@ -5517,7 +5553,8 @@
             if (!(action & UDNS_WAB_REG_QUERY) && (ptr->flag & SLE_WAB_REG_QUERY_STARTED) &&
                 !SameDomainName(&ptr->domain, &localdomain) && (ptr->InterfaceID == mDNSInterface_Any))
             {
-                LogInfo("uDNS_SetupWABQueries: Deleting Registration for domain  %##s", ptr->domain.c);
+                LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
+                    "uDNS_SetupWABQueries: Deleting Registration for domain -- name hash: %x", nameHash);
                 ptr->flag &= ~SLE_WAB_REG_QUERY_STARTED;
                 uDNS_DeleteWABQueries(m, ptr, UDNS_WAB_REG_QUERY);
             }
@@ -5535,22 +5572,26 @@
                 err1 = mDNS_GetDomains(m, &ptr->BrowseQ,          mDNS_DomainTypeBrowse,              &ptr->domain, ptr->InterfaceID, FoundDomain, ptr);
                 if (err1)
                 {
-                    LogMsg("uDNS_SetupWABQueries: GetDomains for domain %##s returned error(s):\n"
-                           "%d (mDNS_DomainTypeBrowse)\n", ptr->domain.c, err1);
+                    LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_ERROR,
+                        "uDNS_SetupWABQueries: GetDomains(mDNS_DomainTypeBrowse) returned error -- "
+                        "name hash: %x, error: %d", nameHash, err1);
                 }
                 else
                 {
-                    LogInfo("uDNS_SetupWABQueries: Starting Browse for domain %##s", ptr->domain.c);
+                    LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
+                        "uDNS_SetupWABQueries: Starting Browse for domain -- name hash: %x", nameHash);
                 }
                 err2 = mDNS_GetDomains(m, &ptr->DefBrowseQ,       mDNS_DomainTypeBrowseDefault,       &ptr->domain, ptr->InterfaceID, FoundDomain, ptr);
                 if (err2)
                 {
-                    LogMsg("uDNS_SetupWABQueries: GetDomains for domain %##s returned error(s):\n"
-                           "%d (mDNS_DomainTypeBrowseDefault)\n", ptr->domain.c, err2);
+                    LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_ERROR,
+                        "uDNS_SetupWABQueries: GetDomains(mDNS_DomainTypeBrowseDefault) returned error -- "
+                        "name hash: %x, error: %d", nameHash, err2);
                 }
                 else
                 {
-                    LogInfo("uDNS_SetupWABQueries: Starting Default Browse for domain %##s", ptr->domain.c);
+                    LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
+                        "uDNS_SetupWABQueries: Starting Default Browse for domain -- name hash: %x", nameHash);
                 }
                 // For simplicity, we mark a single bit for denoting that both the browse queries have started.
                 // It is not clear as to why one would fail to start and the other would succeed in starting up.
@@ -5572,14 +5613,15 @@
                 err1 = mDNS_GetDomains(m, &ptr->AutomaticBrowseQ, mDNS_DomainTypeBrowseAutomatic,     &ptr->domain, ptr->InterfaceID, FoundDomain, ptr);
                 if (err1)
                 {
-                    LogMsg("uDNS_SetupWABQueries: GetDomains for domain %##s returned error(s):\n"
-                           "%d (mDNS_DomainTypeBrowseAutomatic)\n",
-                           ptr->domain.c, err1);
+                    LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_ERROR,
+                        "uDNS_SetupWABQueries: GetDomains(mDNS_DomainTypeBrowseAutomatic) returned error -- "
+                        "name hash: %x, error: %d", nameHash, err1);
                 }
                 else
                 {
                     ptr->flag |= SLE_WAB_LBROWSE_QUERY_STARTED;
-                    LogInfo("uDNS_SetupWABQueries: Starting Legacy Browse for domain %##s", ptr->domain.c);
+                    LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
+                        "uDNS_SetupWABQueries: Starting Legacy Browse for domain -- name hash: %x", nameHash);
                 }
             }
         }
@@ -5593,22 +5635,26 @@
                 err1 = mDNS_GetDomains(m, &ptr->RegisterQ,        mDNS_DomainTypeRegistration,        &ptr->domain, ptr->InterfaceID, FoundDomain, ptr);
                 if (err1)
                 {
-                    LogMsg("uDNS_SetupWABQueries: GetDomains for domain %##s returned error(s):\n"
-                           "%d (mDNS_DomainTypeRegistration)\n", ptr->domain.c, err1);
+                    LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_ERROR,
+                        "uDNS_SetupWABQueries: GetDomains(mDNS_DomainTypeRegistration) returned error -- "
+                        "name hash: %x, error: %d", nameHash, err1);
                 }
                 else
                 {
-                    LogInfo("uDNS_SetupWABQueries: Starting Registration for domain %##s", ptr->domain.c);
+                    LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
+                        "uDNS_SetupWABQueries: Starting Registration for domain -- name hash: %x", nameHash);
                 }
                 err2 = mDNS_GetDomains(m, &ptr->DefRegisterQ,     mDNS_DomainTypeRegistrationDefault, &ptr->domain, ptr->InterfaceID, FoundDomain, ptr);
                 if (err2)
                 {
-                    LogMsg("uDNS_SetupWABQueries: GetDomains for domain %##s returned error(s):\n"
-                           "%d (mDNS_DomainTypeRegistrationDefault)", ptr->domain.c, err2);
+                    LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_ERROR,
+                        "uDNS_SetupWABQueries: GetDomains(mDNS_DomainTypeRegistrationDefault) returned error -- "
+                        "name hash: %x, error: %d", nameHash, err2);
                 }
                 else
                 {
-                    LogInfo("uDNS_SetupWABQueries: Starting Default Registration for domain %##s", ptr->domain.c);
+                    LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
+                        "uDNS_SetupWABQueries: Starting Default Registration for domain -- name hash: %x", nameHash);
                 }
                 if (!err1 || !err2)
                 {
@@ -5685,10 +5731,22 @@
     while (p)
     {
         int labels = CountLabels(&p->domain);
-        if (labels > 0)
+        if (labels > 1)
+        {
+            const domainname *d = SkipLeadingLabels(&p->domain, labels - 2);
+            if (SameDomainName(d, (const domainname *)"\x7" "in-addr" "\x4" "arpa") ||
+                SameDomainName(d, (const domainname *)"\x3" "ip6"     "\x4" "arpa"))
+            {
+                LogInfo("uDNS_GetNextSearchDomain: skipping search domain %##s, InterfaceID %p", p->domain.c, p->InterfaceID);
+                (*searchIndex)++;
+                p = p->next;
+                continue;
+            }
+        }
+        if (ignoreDotLocal && labels > 0)
         {
             const domainname *d = SkipLeadingLabels(&p->domain, labels - 1);
-            if (ignoreDotLocal && SameDomainLabel(d->c, (const mDNSu8 *)"\x5" "local"))
+            if (SameDomainLabel(d->c, (const mDNSu8 *)"\x5" "local"))
             {
                 LogInfo("uDNS_GetNextSearchDomain: skipping local domain %##s, InterfaceID %p", p->domain.c, p->InterfaceID);
                 (*searchIndex)++;
@@ -5814,6 +5872,7 @@
     CacheRecord *CacheFlushRecords = (CacheRecord*)1;
     CacheRecord **cfp = &CacheFlushRecords;
     enum { removeName, removeClass, removeRRset, removeRR, addRR } action;
+    const mDNSInterfaceID if_id = DNSPushServerGetInterfaceID(m, server);
 
     // Ignore records we don't want to cache.
 
@@ -5933,7 +5992,7 @@
             CacheRecord *rr = mDNSNULL;
 
             // 2a. Check if this packet resource record is already in our cache.
-            rr = mDNSCoreReceiveCacheCheck(m, msg, uDNS_LLQ_Events, slot, cg, &cfp, mDNSNULL);
+            rr = mDNSCoreReceiveCacheCheck(m, msg, uDNS_LLQ_Events, slot, cg, &cfp, if_id);
 
             // If packet resource record not in our cache, add it now
             // (unless it is just a deletion of a record we never had, in which case we don't care)
@@ -5961,12 +6020,13 @@
     mDNSIPPort port;
     port.NotAnInteger = 0;
     ResourceRecord *const mrr = &m->rec.r.resrec;
+    const mDNSInterfaceID if_id = DNSPushServerGetInterfaceID(m, server);
 
     // Validate the contents of the message
     // XXX Right now this code will happily parse all the valid data and then hit invalid data
     // and give up.  I don't think there's a risk here, but we should discuss it.
     // XXX what about source validation?   Like, if we have a VPN, are we safe?   I think yes, but let's think about it.
-    while ((ptr = GetLargeResourceRecord(m, msg, ptr, end, mDNSNULL, kDNSRecordTypePacketAns, &m->rec)))
+    while ((ptr = GetLargeResourceRecord(m, msg, ptr, end, if_id, kDNSRecordTypePacketAns, &m->rec)))
     {
     #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
         mdns_forget(&mrr->metadata);
@@ -6825,6 +6885,15 @@
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     mdns_replace(&server->dnsservice, dnsService);
+#if MDNSRESPONDER_SUPPORTS(APPLE, TERMINUS_ASSISTED_UNICAST_DISCOVERY)
+    if (server->connectInfo)
+    {
+        // If the underlying DNS service is an mDNS alternative service, then it might have configured alternative TLS
+        // trust anchors for us to use when setting up the TLS connection.
+        server->connectInfo->trusts_alternative_server_certificates =
+            mdns_dns_service_is_mdns_alternative(dnsService);
+    }
+#endif
 #else
     server->qDNSServer = qDNSServer;
 #endif
@@ -6846,6 +6915,33 @@
     return serverToReturn;
 }
 
+mDNSexport mDNSInterfaceID DNSPushServerGetInterfaceID(mDNS *const m, const DNSPushServer *const server)
+{
+    mDNSInterfaceID ifID = mDNSInterface_Any;
+    if (server)
+    {
+        bool hasLocalPurview = false;
+    #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+        if (server->dnsservice)
+        {
+            // Check if the underlying DNS service of this DNS push server has local purview
+            hasLocalPurview = mdns_dns_service_has_local_purview(server->dnsservice);
+        }
+    #endif
+        if (hasLocalPurview && server->connectInfo)
+        {
+            // If the underlying DNS service has local purview, then this server is specifically related to the
+            // interface where the DSO connection is established.
+            const dso_connect_state_t *const dcs = server->connectInfo;
+            if (dcs->if_idx != 0)
+            {
+                ifID = mDNSPlatformInterfaceIDfromInterfaceIndex(m, dcs->if_idx);
+            }
+        }
+    }
+    return ifID;
+}
+
 mDNSexport void DNSPushServerFinalize(DNSPushServer *const server)
 {
     LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "[PushS%u] DNSPushServer finalizing - "
@@ -7030,13 +7126,13 @@
             continue;
         }
 
-        // When a subscription is canceled, this original TTL needs to be restored and the record aging resumes.
-        // See https://tools.ietf.org/html/rfc8765#section-6.3.1: "The TTL of an added record is stored by the client."
+        // When a subscription is canceled, the added record will be removed immediately from the cache.
         cache_record->DNSPushSubscribed = mDNSfalse;
         LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
-            "[Q%u->PushS%u->DSO%u] the cached record aging resumes due to the unsubscribed activity - "
+            "[Q%u->PushS%u->DSO%u] Removing record from the cache due to the unsubscribed activity - "
             "qname: " PRI_DM_NAME ", qtype: " PUB_S ", TTL: %us.", qid, server_serial, dso_serial,
             DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype), cache_record->resrec.rroriginalttl);
+        mDNS_PurgeCacheResourceRecord(m, cache_record);
     }
 
 exit:
@@ -7080,12 +7176,16 @@
     }
     if (primary->dnsPushServer != mDNSNULL)
     {
-        if (primary->dnsPushServer->connection != mDNSNULL)
+        dso_state_t *const dso_state = primary->dnsPushServer->connection;
+        if (dso_state)
         {
+            // Update the outstanding query context from the old primary being stopped to the new primary.
+            dso_update_outstanding_query_context(dso_state, primary, duplicate);
+
             // Also update the context of the dso_activity_t since we are replacing the original primary question with
             // the new one(which is previously a duplicate of the primary question).
-            dso_activity_t *const activity = dso_find_activity(primary->dnsPushServer->connection, mDNSNULL,
-                                                               kDNSPushActivity_Subscription, primary);
+            dso_activity_t *const activity = dso_find_activity(dso_state, mDNSNULL, kDNSPushActivity_Subscription,
+                primary);
             if (activity != mDNSNULL)
             {
                 activity->context = duplicate;
@@ -7133,6 +7233,8 @@
         CASE_TO_STR(LLQ_SecondaryRequest);
         CASE_TO_STR(LLQ_Established);
         CASE_TO_STR(LLQ_Poll);
+        MDNS_COVERED_SWITCH_DEFAULT:
+            break;
     }
 #undef CASE_TO_STR
     LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_FAULT, "Invalid LLQ_State - state: %u", state);
diff --git a/mDNSCore/uDNS.h b/mDNSCore/uDNS.h
index 45e1764..d529d5e 100644
--- a/mDNSCore/uDNS.h
+++ b/mDNSCore/uDNS.h
@@ -36,7 +36,8 @@
                                                              // which typically heal quickly, so we start agressively and exponentially back off
 #define MAX_UCAST_POLL_INTERVAL (60 * 60 * mDNSPlatformOneSecond)
 //#define MAX_UCAST_POLL_INTERVAL (1 * 60 * mDNSPlatformOneSecond)
-#define LLQ_POLL_INTERVAL       (15 * 60 * mDNSPlatformOneSecond) // Polling interval for zones w/ an advertised LLQ port (ie not static zones) if LLQ fails due to NAT, etc.
+#define LLQ_POLL_INTERVAL_MIN   15
+#define LLQ_POLL_INTERVAL       (LLQ_POLL_INTERVAL_MIN * 60 * mDNSPlatformOneSecond) // Polling interval for zones w/ an advertised LLQ port (ie not static zones) if LLQ fails due to NAT, etc.
 #define RESPONSE_WINDOW (60 * mDNSPlatformOneSecond)         // require server responses within one minute of request
 #define MAX_UCAST_UNANSWERED_QUERIES 2                       // number of unanswered queries from any one uDNS server before trying another server
 #define DNSSERVER_PENALTY_TIME (60 * mDNSPlatformOneSecond)  // number of seconds for which new questions don't pick this server
@@ -138,11 +139,15 @@
 extern void UnsubscribeAllQuestionsFromDNSPushServer(mDNS *m, DNSPushServer *server);
 extern void DNSPushZoneRemove(mDNS *m, const DNSPushServer *server);
 extern void DNSPushZoneFinalize(DNSPushZone *zone);
+extern mDNSInterfaceID DNSPushServerGetInterfaceID(mDNS *m, const DNSPushServer *server);
 extern void DNSPushServerCancel(DNSPushServer *server, mDNSBool alreadyRemovedFromSystem);
 extern void DNSPushServerFinalize(DNSPushServer *server);
 extern void DNSPushUpdateQuestionDuplicate(DNSQuestion *primary, DNSQuestion *duplicate);
 #endif
 
+extern void GetZoneData_QuestionCallback(mDNS *m, DNSQuestion *question, const ResourceRecord *answer,
+    QC_result AddRecord);
+
 extern void SleepRecordRegistrations(mDNS *m);
 
 // uDNS_UpdateRecord
diff --git a/mDNSMacOSX/ApplePlatformFeatures.h b/mDNSMacOSX/ApplePlatformFeatures.h
index 31e4329..378b1f9 100644
--- a/mDNSMacOSX/ApplePlatformFeatures.h
+++ b/mDNSMacOSX/ApplePlatformFeatures.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2018-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,6 +17,8 @@
 #ifndef __ApplePlatformFeatures_h
 #define __ApplePlatformFeatures_h
 
+#include <AppleFeatures/AppleFeatures.h>
+#include <mdns/general.h>
 #include <TargetConditionals.h>
 
 // Feature: Add audit token to questions
@@ -43,6 +45,7 @@
     #define MDNSRESPONDER_SUPPORTS_APPLE_AWDL_FAST_CACHE_FLUSH      1
 #endif
 
+
 // Feature: Bonjour-On-Demand
 // Radar:   <rdar://problem/23523784>
 // Enabled: Yes.
@@ -89,6 +92,14 @@
     #define MDNSRESPONDER_SUPPORTS_APPLE_D2D                        1
 #endif
 
+// Feature: Discovery Proxy client support for local domains
+// Radar:   <rdar://114127909>
+// Enabled: Support is compiled in, but disabled by default by the discovery_proxy_client runtime feature flag.
+
+#if !defined(MDNSRESPONDER_SUPPORTS_APPLE_DISCOVERY_PROXY_CLIENT)
+    #define MDNSRESPONDER_SUPPORTS_APPLE_DISCOVERY_PROXY_CLIENT     1
+#endif
+
 // Feature: Support for DNS Analytics
 // Radar:   <rdar://problem/57972792>, <rdar://problem/57970914>
 // Enabled: iOS & macOS
@@ -166,6 +177,18 @@
     #endif
 #endif
 
+// Feature: Don't pretend that the network interface addresses went away in preparation for sleep.
+// Radar:   <rdar://127413333>
+// Enabled: Only on watchOS for now.
+
+#if !defined(MDNSRESPONDER_SUPPORTS_APPLE_KEEP_INTERFACES_DURING_SLEEP)
+    #if MDNS_OS(watchOS)
+        #define MDNSRESPONDER_SUPPORTS_APPLE_KEEP_INTERFACES_DURING_SLEEP 1
+    #else
+        #define MDNSRESPONDER_SUPPORTS_APPLE_KEEP_INTERFACES_DURING_SLEEP 0
+    #endif
+#endif
+
 // Feature: Change privacy level of logs and state dump on the internal build.
 // Radar:   <rdar://79636882>
 // Enabled: On all internal Apple platforms.
@@ -220,6 +243,14 @@
     #define MDNSRESPONDER_SUPPORTS_APPLE_PADDING_CHECKS             1
 #endif
 
+// Feature: Powerlog mDNS client requests.
+// Radar:   <rdar://112118989>
+// Enabled: Yes.
+
+#if !defined(MDNSRESPONDER_SUPPORTS_APPLE_POWERLOG_MDNS_REQUESTS)
+    #define MDNSRESPONDER_SUPPORTS_APPLE_POWERLOG_MDNS_REQUESTS     1
+#endif
+
 // Feature: Use mdns_querier objects for DNS transports.
 // Radar:   <rdar://problem/55746371>
 // Enabled: Yes.
@@ -292,6 +323,14 @@
     #define MDNSRESPONDER_SUPPORTS_APPLE_SYMPTOMS                   1
 #endif
 
+// Feature: Terminus Assisted Unicast Discovery
+// Radar:   <rdar://115756005>
+// Enabled: Yes.
+
+#if !defined(MDNSRESPONDER_SUPPORTS_APPLE_TERMINUS_ASSISTED_UNICAST_DISCOVERY)
+    #define MDNSRESPONDER_SUPPORTS_APPLE_TERMINUS_ASSISTED_UNICAST_DISCOVERY    1
+#endif
+
 // Feature: Tracker Debugging
 // Radar:   <rdar://problem/102778582>
 // Enabled: Yes. (depends on MDNSRESPONDER_SUPPORTS_APPLE_TRACKER_STATE)
@@ -317,11 +356,11 @@
 #endif
 
 // Feature: Enforce entitlements prompts
-// Radar:   <rdar://problem/55922132>
-// Enabled: iOS only (depends on MDNSRESPONDER_SUPPORTS_APPLE_AUDIT_TOKEN)
+// Radar:   <rdar://problem/55922132>, <rdar://problem/113918221>
+// Enabled: iOS & macOS (depends on MDNSRESPONDER_SUPPORTS_APPLE_AUDIT_TOKEN)
 
 #if !defined(MDNSRESPONDER_SUPPORTS_APPLE_TRUST_ENFORCEMENT)
-    #if (TARGET_OS_IOS)
+    #if (TARGET_OS_IOS || TARGET_OS_OSX)
         #define MDNSRESPONDER_SUPPORTS_APPLE_TRUST_ENFORCEMENT      1
     #else
         #define MDNSRESPONDER_SUPPORTS_APPLE_TRUST_ENFORCEMENT      0
@@ -422,12 +461,30 @@
     #endif
 #endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, DISCOVERY_PROXY_CLIENT)
+    #if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+        #error "MDNSRESPONDER_SUPPORTS(APPLE, DISCOVERY_PROXY_CLIENT) depends on MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)."
+    #endif
+#endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
+    #if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+        #error "MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH) depends on MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)."
+    #endif
+#endif
+
 #if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
     #if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER) && !MDNSRESPONDER_DISABLE_DNSSECv2_DEPENDENCY_CHECK_FOR_QUERIER
         #error "MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2) depends on MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)."
     #endif
 #endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
+    #if !MDNSRESPONDER_SUPPORTS(APPLE, AWDL)
+        #error "MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS) depends on MDNSRESPONDER_SUPPORTS(APPLE, AWDL)."
+    #endif
+#endif
+
 #if MDNSRESPONDER_SUPPORTS(APPLE, RANDOM_AWDL_HOSTNAME)
     #if !MDNSRESPONDER_SUPPORTS(APPLE, AWDL)
         #error "MDNSRESPONDER_SUPPORTS(APPLE, RANDOM_AWDL_HOSTNAME) depends on MDNSRESPONDER_SUPPORTS(APPLE, AWDL)."
@@ -440,6 +497,12 @@
     #endif
 #endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, TERMINUS_ASSISTED_UNICAST_DISCOVERY)
+    #if !MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+        #error "MDNSRESPONDER_SUPPORTS(APPLE, TERMINUS_ASSISTED_UNICAST_DISCOVERY) depends on MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)."
+    #endif
+#endif
+
 #if MDNSRESPONDER_SUPPORTS(APPLE, TRACKER_DEBUGGING)
     #if !MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
         #error "MDNSRESPONDER_SUPPORTS(APPLE, TRACKER_DEBUGGING) depends on MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)."
diff --git a/mDNSMacOSX/BonjourEvents.c b/mDNSMacOSX/BonjourEvents.c
index 1257d83..a898ad2 100644
--- a/mDNSMacOSX/BonjourEvents.c
+++ b/mDNSMacOSX/BonjourEvents.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2010-2015, 2020 Apple Inc. All rights reserved.
+ * Copyright (c) 2010-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@
 #include <stdlib.h>
 #include <os/log.h>
 #include <xpc/xpc.h>
+#include <mdns/general.h>
 
 
 #pragma mark -
@@ -332,7 +333,7 @@
 * This is invoked when launchd loads a event dictionary and needs to inform
 * us what a daemon / agent is looking for.
 *****************************************************************************/
-static void ManageEventsCallback(UserEventAgentLaunchdAction action, CFNumberRef token, CFTypeRef eventMatchDict, void* vContext)
+static void LaunchdActionHandler(UserEventAgentLaunchdAction action, CFNumberRef token, CFTypeRef eventMatchDict, void* vContext)
 {
     if (action == kUserEventAgentLaunchdAdd)
     {
@@ -363,6 +364,33 @@
     }
 }
 
+static void ManageEventsCallback(const UserEventAgentLaunchdAction action, const CFNumberRef token,
+    const CFTypeRef eventMatchDict, void* const vContext)
+{
+    // It used to be the case that the callback passed to UserEventAgentRegisterForLaunchEvents(), i.e.,
+    // ManageEventsCallback(), would be guaranteed to be invoked on the main queue. However, that hasn't been true since
+    // 2011. See source changes for <rdar://9179704>.
+    //
+    // The UserEventAgent* API, which has been deprecated since 2011, doesn't provide a way to specify a dispatch queue,
+    // for the callback. Since LaunchdActionHandler() deals with the same plugin data structures as
+    // ServiceBrowserCallback(), which runs on the main queue, LaunchdActionHandler() must run on the same queue.
+    //
+    // The lowest-risk short-term solution is to simply schedule LaunchdActionHandler() to run on the main queue. In the
+    // long term, the UserEventAgent* API should be replaced with the xpc_event_provider_* API, which is the official
+    // replacement API, along with moving away from the main queue. Note that the UserEventAgent* API hasn't been
+    // decorated with any deprecation attributes, which normally trigger build warnings, which is probably why the
+    // deprecation went unnoticed for so long.
+    mdns_cf_retain_null_safe(token);
+    mdns_cf_retain_null_safe(eventMatchDict);
+    dispatch_async(dispatch_get_main_queue(),
+    ^{
+        LaunchdActionHandler(action, token, eventMatchDict, vContext);
+        CFNumberRef tokenTmp = token;
+        mdns_cf_forget(&tokenTmp);
+        CFTypeRef eventMatchDictTmp = eventMatchDict;
+        mdns_cf_forget(&eventMatchDictTmp);
+    });
+}
 
 #pragma mark -
 #pragma mark Plugin Guts
@@ -537,12 +565,11 @@
         os_log_info(OS_LOG_DEFAULT, "%s:%s: Removing browser %p from _browsers", sPluginIdentifier, __FUNCTION__, browser);
         CFDictionaryRemoveValue(plugin->_browsers, browser); // This triggers release and dealloc of the browser
     }
-    else
-    {
-        os_log_info(OS_LOG_DEFAULT, "%s:%s: Decrementing browsers %p count", sPluginIdentifier, __FUNCTION__, browser);
-        // Decrement my reference count (it was incremented when it was added to _browsers in CreateBrowser)
-        NetBrowserInfoRelease(NULL, browser);
-    }
+
+    // Decrement my reference count. The reference is either the original +1 reference from NetBrowserInfoCreate() or a
+    // +1 retain by CreateBrowser().
+    os_log_info(OS_LOG_DEFAULT, "%s:%s: Decrementing browser %p count", sPluginIdentifier, __FUNCTION__, browser);
+    NetBrowserInfoRelease(NULL, browser);
 
     free(browsers);
 }
diff --git a/mDNSMacOSX/D2D.c b/mDNSMacOSX/D2D.c
index 1a14970..cc58376 100644
--- a/mDNSMacOSX/D2D.c
+++ b/mDNSMacOSX/D2D.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2002-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -274,7 +274,7 @@
     }
     (*ptr)->refCount += 1;
 
-    LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "D2DBrowseListRetain - "
+    LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEBUG, "D2DBrowseListRetain - "
         "name: " PRI_DM_NAME ", type: " PUB_DNS_TYPE ", ref count: %u", DM_NAME_PARAM(&(*ptr)->name),
         DNS_TYPE_PARAM((*ptr)->type), (*ptr)->refCount);
 }
@@ -286,14 +286,14 @@
 
     if (!*ptr)
     {
-        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "D2DBrowseListRelease item not found in the list - "
+        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEBUG, "D2DBrowseListRelease item not found in the list - "
             "name: " PRI_DM_NAME ", type: " PUB_DNS_TYPE, DM_NAME_PARAM(name), DNS_TYPE_PARAM(type));
         return false;
     }
 
     (*ptr)->refCount -= 1;
 
-    LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "D2DBrowseListRelease - "
+    LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEBUG, "D2DBrowseListRelease - "
         "name: " PRI_DM_NAME ", type: " PUB_DNS_TYPE ", ref count: %u", DM_NAME_PARAM(&(*ptr)->name),
         DNS_TYPE_PARAM((*ptr)->type), (*ptr)->refCount);
 
@@ -434,7 +434,7 @@
     }
     if (mDNS_LoggingEnabled)
     {
-        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "xD2DParseCompressedPacket: Our Bytes - name: " PRI_DM_NAME
+        LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEBUG, "xD2DParseCompressedPacket: Our Bytes - name: " PRI_DM_NAME
             ", type: " PUB_DNS_TYPE ", TTL: %u, rdata length: %u", DM_NAME_PARAM(&recordName),
             DNS_TYPE_PARAM(recordType), ttl, rhs_len);
     }
@@ -744,10 +744,8 @@
     return;
 }
 
-mDNSlocal void xD2DServiceCallback(D2DServiceEvent event, D2DStatus result, D2DServiceInstance instanceHandle, D2DTransportType transportType, const Byte *key, size_t keySize, const Byte *value, size_t valueSize, void *userData)
+mDNSlocal void xD2DServiceCallback(D2DServiceEvent event, D2DStatus result, D2DServiceInstance instanceHandle, D2DTransportType transportType, const Byte *key, size_t keySize, const Byte *value, size_t valueSize, void * __unused userData)
 {
-    const char *eventString = "unknown";
-
     KQueueLock();
 
     if (keySize   > 0xFFFF)
@@ -761,33 +759,7 @@
             "value size: %zu", valueSize);
     }
 
-    switch (event)
-    {
-    case D2DServiceFound:
-        eventString = "D2DServiceFound";
-        break;
-    case D2DServiceLost:
-        eventString = "D2DServiceLost";
-        break;
-    case D2DServiceResolved:
-        eventString = "D2DServiceResolved";
-        break;
-    case D2DServiceRetained:
-        eventString = "D2DServiceRetained";
-        break;
-    case D2DServiceReleased:
-        eventString = "D2DServiceReleased";
-        break;
-    case D2DServicePeerLost:
-        eventString = "D2DServicePeerLost";
-        break;
-    default:
-        break;
-    }
-
-    LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "xD2DServiceCallback - event: " PUB_S
-        ", result: %d, instanceHandle: %p, transportType: %u, LHS: %p (%zu), RHS: %p (%zu), userData: %p",
-        eventString, result, instanceHandle, transportType, key, keySize, value, valueSize, userData);
+    LogRedact(MDNS_LOG_CATEGORY_D2D, MDNS_LOG_DEFAULT, "xD2DServiceCallback -- event: " PUB_D2D_SRV_EVENT, event);
     PrintHelper(__func__, key, (mDNSu16)keySize, value, (mDNSu16)valueSize);
 
     switch (event)
diff --git a/mDNSMacOSX/DNS64.c b/mDNSMacOSX/DNS64.c
index 1ec0370..d37aea1 100644
--- a/mDNSMacOSX/DNS64.c
+++ b/mDNSMacOSX/DNS64.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017-2022 Apple Inc. All rights reserved.
+ * Copyright (c) 2017-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -183,9 +183,7 @@
     case kDNS64State_ReverseIPv6:
         break;
 
-    CUClangWarningIgnoreBegin(-Wcovered-switch-default);
-    default:
-    CUClangWarningIgnoreEnd();
+    MDNS_COVERED_SWITCH_DEFAULT:
         LogMsg("DNS64StateMachine: unrecognized DNS64 state %d", inQ->dns64.state);
         break;
     }
@@ -330,9 +328,7 @@
     case kDNS64State_ReverseIPv6:
         break;
 
-    CUClangWarningIgnoreBegin(-Wcovered-switch-default);
-    default:
-    CUClangWarningIgnoreEnd();
+    MDNS_COVERED_SWITCH_DEFAULT:
         LogMsg("DNS64ResetState: unrecognized DNS64 state %d", inQ->dns64.state);
         break;
     }
@@ -549,9 +545,7 @@
         inQ->qtype = kDNSType_AAAA;
         break;
 
-    CUClangWarningIgnoreBegin(-Wcovered-switch-default);
-    default:
-    CUClangWarningIgnoreEnd();
+    MDNS_COVERED_SWITCH_DEFAULT:
         LogMsg("DNS64RestartQuestion: unrecognized DNS64 state %d", inQ->dns64.state);
         break;
     }
diff --git a/mDNSMacOSX/LegacyNATTraversal.c b/mDNSMacOSX/LegacyNATTraversal.c
index 1618630..826dd1a 100644
--- a/mDNSMacOSX/LegacyNATTraversal.c
+++ b/mDNSMacOSX/LegacyNATTraversal.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2004-2019, 2022-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2004-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -456,18 +456,11 @@
         case LNTExternalAddrOp:  handleLNTGetExternalAddressResponse(tcpInfo); break;
         case LNTPortMapOp:       handleLNTPortMappingResponse       (tcpInfo); break;
         case LNTPortMapDeleteOp: status = mStatus_ConfigChanged;               break;
-#ifdef __clang__
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wcovered-switch-default"
-#endif
-        default:
+        MDNS_COVERED_SWITCH_DEFAULT:
             LogRedact(MDNS_LOG_CATEGORY_NAT, MDNS_LOG_DEFAULT, "tcpConnectionCallback: bad tcp operation! %d",
                 tcpInfo->op);
             status = mStatus_Invalid;
             break;
-#ifdef __clang__
-#pragma clang diagnostic pop
-#endif
         }
     }
 exit:
@@ -513,6 +506,7 @@
             }
             break;
         case LNTPortMapDeleteOp: break;
+        MDNS_COVERED_SWITCH_DEFAULT: break;
         }
 
         mDNSPlatformTCPCloseConnection(sock);
diff --git a/mDNSMacOSX/QuerierSupport.c b/mDNSMacOSX/QuerierSupport.c
index 007739a..6b79ad9 100644
--- a/mDNSMacOSX/QuerierSupport.c
+++ b/mDNSMacOSX/QuerierSupport.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2019-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -61,8 +61,6 @@
 
 mDNSlocal void _Querier_HandleSubscriberInvalidation(mdns_subscriber_t subscriber);
 
-mDNSlocal mDNSBool _Querier_QuestionBelongsToSelf(const DNSQuestion *q);
-
 mDNSlocal void _Querier_LogDNSServices(const mdns_dns_service_manager_t manager)
 {
     __block mDNSu32 count = 0;
@@ -106,7 +104,7 @@
             mDNS_Lock(m);
             // Check if outstanding DNSQuestions need to switch to a different DNS service now that an NEDNSProxy is
             // running or no longer running.
-            Querier_ProcessDNSServiceChanges();
+            Querier_ProcessDNSServiceChanges(mDNSfalse);
             // When a NetworkExtension DNS proxy is running, all Do53 queries are diverted to the DNS proxy by the
             // kernel. Therefore, when there's a state change, we flush all records marked as having come from a Do53
             // service because of either of the following reasons:
@@ -188,7 +186,7 @@
             case mdns_event_update:
                 mdns_dns_service_manager_apply_pending_updates(manager);
                 mDNS_Lock(m);
-                Querier_ProcessDNSServiceChanges();
+                Querier_ProcessDNSServiceChanges(mDNSfalse);
                 _Querier_LogDNSServices(manager);
                 mDNS_Unlock(m);
                 break;
@@ -196,6 +194,9 @@
             case mdns_event_invalidated:
                 mdns_release(manager);
                 break;
+
+            MDNS_COVERED_SWITCH_DEFAULT:
+                break;
         }
         KQueueUnlock("DNS Service Manager event handler");
     });
@@ -205,50 +206,64 @@
     return sDNSServiceManager;
 }
 
-mDNSlocal mdns_dns_service_t _Querier_GetDNSPushDNSService(const mdns_dns_service_manager_t manager,
-    const domainname *const zone, const uint32_t ifIndex)
+#if MDNSRESPONDER_SUPPORTS(APPLE, TERMINUS_ASSISTED_UNICAST_DISCOVERY)
+
+mDNSlocal mdns_dns_service_t _Querier_GetMDNSAlternativeService(const mdns_dns_service_manager_t manager,
+    const DNSQuestion * const q)
 {
-    mdns_dns_service_t service = NULL;
-
-    static mdns_domain_name_t dnsPushSRVDotLocal = NULL;
-    mdns_domain_name_t dnsPushSRV = NULL;
-
-    const bool isLocalDomain = SameDomainName(zone, &localdomain);
-    if (isLocalDomain)
+    mdns_dns_service_t service;
+    if (q->InterfaceID)
     {
-        if (!dnsPushSRVDotLocal)
-        {
-            dnsPushSRVDotLocal = mdns_domain_name_create("_dns‑push‑tls._tcp.local", mdns_domain_name_create_opts_none,
-                NULL);
-            require_quiet(dnsPushSRVDotLocal, exit);
-        }
-        dnsPushSRV = dnsPushSRVDotLocal;
-        mdns_retain(dnsPushSRV);
+        const uint32_t ifIndex = (uint32_t)((uintptr_t)q->InterfaceID);
+        service = mdns_dns_service_manager_get_interface_scoped_mdns_alternative_service(manager, q->qname.c,
+            ifIndex);
     }
     else
     {
-        // "_dns-push-tls._tcp"
-        domainname dnsPushSRVToConstruct = {
-            .c = {13, '_', 'd', 'n', 's', '-', 'p', 'u', 's', 'h', '-', 't', 'l', 's', 4, '_', 't', 'c', 'p', 0}
-        };
-        AppendDomainName(&dnsPushSRVToConstruct, zone);
-        dnsPushSRV = mdns_domain_name_create_with_labels(dnsPushSRVToConstruct.c, NULL);
-        require_quiet(dnsPushSRV, exit);
+        service = mdns_dns_service_manager_get_unscoped_mdns_alternative_service(manager, q->qname.c);
     }
+    return service;
+}
 
-    service = mdns_dns_service_manager_get_push_service(manager, dnsPushSRV, ifIndex);
-    if (!service && isLocalDomain)
-    {
-        mdns_dns_service_manager_register_push_service(manager, dnsPushSRV, ifIndex, NULL);
-        service = mdns_dns_service_manager_get_push_service(manager, dnsPushSRV, ifIndex);
-    }
-    // We don't register the DNS push service if it is not for local domain.
+#endif
+
+mDNSlocal mdns_dns_service_t _Querier_GetDiscoveredPushDNSService(const mdns_dns_service_manager_t manager,
+    const domainname *const zone, const uint32_t ifIndex)
+{
+    mdns_dns_service_t service = mDNSNULL;
+    mdns_domain_name_t dnsPushSRV = mDNSNULL;
+
+    // "_dns-push-tls._tcp"
+    domainname dnsPushSRVToConstruct = {
+        .c = {13, '_', 'd', 'n', 's', '-', 'p', 'u', 's', 'h', '-', 't', 'l', 's', 4, '_', 't', 'c', 'p', 0}
+    };
+    AppendDomainName(&dnsPushSRVToConstruct, zone);
+    dnsPushSRV = mdns_domain_name_create_with_labels(dnsPushSRVToConstruct.c, NULL);
+    mdns_require_quiet(dnsPushSRV, exit);
+
+    service = mdns_dns_service_manager_get_discovered_push_service(manager, dnsPushSRV, ifIndex);
 
 exit:
     mdns_forget(&dnsPushSRV);
     return service;
 }
 
+mDNSlocal mdns_dns_service_t _Querier_GetCustomPushService(const mdns_dns_service_manager_t manager,
+    const DNSQuestion * const q)
+{
+    mdns_dns_service_t service;
+    if (q->InterfaceID)
+    {
+        const uint32_t ifIndex = (uint32_t)((uintptr_t)q->InterfaceID);
+        service = mdns_dns_service_manager_get_interface_scoped_custom_push_service(manager, q->qname.c, ifIndex);
+    }
+    else
+    {
+        service = mdns_dns_service_manager_get_unscoped_custom_push_service(manager, q->qname.c);
+    }
+    return service;
+}
+
 mDNSlocal mdns_dns_service_t _Querier_GetNativeDNSService(const mdns_dns_service_manager_t manager,
     const DNSQuestion * const q)
 {
@@ -361,10 +376,9 @@
     mDNSBool eligible = mDNStrue;
 
 #if MDNSRESPONDER_SUPPORTS(COMMON, LOCAL_DNS_RESOLVER_DISCOVERY)
-    if (IsSubdomain(&q->qname, THREAD_DOMAIN_NAME))
+    if (!IsRootDomain(Do53_UNICAST_DISCOVERY_DOMAIN)
+        && IsSubdomain(&q->qname, Do53_UNICAST_DISCOVERY_DOMAIN))
     {
-        // We do not want the query ends with "openthread.thread.home.arpa." to choose a non-native DNS service to go
-        // outside of the home network.
         eligible = mDNSfalse;
     }
 #endif
@@ -376,32 +390,53 @@
 {
     mdns_dns_service_t service = mDNSNULL;
     const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
-    if (!manager)
-    {
-        return NULL;
-    }
+    mdns_require_quiet(manager, exit);
 
+    if (q->OverrideDNSService)
+    {
+        const uint32_t ifIndex = (uint32_t)((uintptr_t)q->InterfaceID);
+        service = mdns_dns_service_manager_get_uuid_scoped_service(manager, q->ResolverUUID, ifIndex);
+    }
+#if MDNSRESPONDER_SUPPORTS(APPLE, TERMINUS_ASSISTED_UNICAST_DISCOVERY)
+    else if (DNSQuestionRequestsMDNSAlternativeService(q) && !Querier_QuestionBelongsToSelf(q))
+    {
+        service = _Querier_GetMDNSAlternativeService(manager, q);
+    }
+#endif
 #if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
-    if (dns_question_enables_dns_push(q))
+    // If DNS question uses DNS Push but it falls back to DNS polling, then we should skip DNS push and use Do53 instead
+    // for DNS polling.
+    else if (dns_question_uses_dns_push(q) && (!dns_question_uses_dns_polling(q)))
     {
         // Only get DNS push DNS service when the DNS push enabled question has finished discovery process.
-        const dns_obj_domain_name_t zone = dns_question_get_authoritative_zone(q);
-        if (zone)
+        if (!dns_question_finished_push_discovery(q))
         {
-            const domainname *const zoneInDomainName = (const domainname *)(dns_obj_domain_name_get_labels(zone));
-            // Always use interface index 0 for the normal DNS push operation.
-            service = _Querier_GetDNSPushDNSService(manager, zoneInDomainName, 0);
+            // Otherwise, the service should be NULL so that the question can be suppressed before it finishes the
+            // discovery.
+            goto exit;
         }
+        const dns_obj_domain_name_t zone = dns_question_get_authoritative_zone(q);
+        mdns_require_quiet(zone, exit);
+
+        const domainname *const zoneInDomainName = (const domainname *)(dns_obj_domain_name_get_labels(zone));
+        const uint32_t ifIndex = (uint32_t)((uintptr_t)q->InterfaceID);
+        service = _Querier_GetDiscoveredPushDNSService(manager, zoneInDomainName, ifIndex);
     }
-    else
 #endif
+    else
     {
-        service = _Querier_GetNativeDNSService(manager, q);
+        service = _Querier_GetCustomPushService(manager, q);
+        if (!service)
+        {
+            service = _Querier_GetNativeDNSService(manager, q);
+        }
         if (!service && _Querier_QuestionIsEligibleForNonNativeDNSService(q))
         {
             service = _Querier_GetNonNativeDNSService(manager, q, excludeEncryptedDNS);
         }
     }
+
+exit:
     return service;
 }
 
@@ -427,22 +462,6 @@
     return sUUID;
 }
 
-mDNSlocal mDNSBool _Querier_QuestionBelongsToSelf(const DNSQuestion *q)
-{
-    if (q->ProxyQuestion)
-    {
-        return mDNSfalse;
-    }
-    if (q->pid != 0)
-    {
-        return ((q->pid == _Querier_GetMyPID()) ? mDNStrue : mDNSfalse);
-    }
-    else
-    {
-        return ((uuid_compare(q->uuid, _Querier_GetMyUUID()) == 0) ? mDNStrue : mDNSfalse);
-    }
-}
-
 mDNSlocal mDNSBool _Querier_DNSServiceIsUnscopedAndLacksPrivacy(const mdns_dns_service_t service)
 {
     if ((mdns_dns_service_get_scope(service) == mdns_dns_service_scope_none) &&
@@ -512,7 +531,7 @@
 // e.g., those specified via DHCP.
 mDNSlocal mDNSBool _Querier_ExcludeEncryptedDNSServices(const DNSQuestion *const q)
 {
-    return (_Querier_QuestionBelongsToSelf(q) || q->ProhibitEncryptedDNS || IsLocalDomain(&q->qname));
+    return (Querier_QuestionBelongsToSelf(q) || q->ProhibitEncryptedDNS || IsLocalDomain(&q->qname));
 }
 
 mDNSexport void Querier_SetDNSServiceForQuestion(DNSQuestion *q)
@@ -591,12 +610,39 @@
     else
     {
         LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
-            "[R%u->Q%u] Question for " PRI_DM_NAME " (" PUB_S PUB_S ") assigned DNS service %llu",
-            q->request_id, mDNSVal16(q->TargetQID), DM_NAME_PARAM(&q->qname), DNSTypeName(q->qtype),
-            enablesDNSSEC ? ", DNSSEC" : "", mdns_dns_service_get_id(q->dnsservice));
+            "[R%u->Q%u] Question assigned DNS service %llu",
+            q->request_id, mDNSVal16(q->TargetQID), mdns_dns_service_get_id(q->dnsservice));
     }
 }
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, TERMINUS_ASSISTED_UNICAST_DISCOVERY)
+
+mDNSexport mDNSBool Querier_IsMDNSAlternativeServiceAvailableForQuestion(const DNSQuestion * const q)
+{
+    mdns_dns_service_t mdns_exclusive_service = mDNSNULL;
+
+    const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
+    if (manager)
+    {
+        mdns_exclusive_service = _Querier_GetMDNSAlternativeService(manager, q);
+    }
+    return (mdns_exclusive_service != mDNSNULL);
+}
+
+#endif
+
+mDNSexport mDNSBool Querier_IsCustomPushServiceAvailableForQuestion(const DNSQuestion * const q)
+{
+    mdns_dns_service_t custom_push_service = mDNSNULL;
+
+    const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
+    if (manager)
+    {
+        custom_push_service = _Querier_GetCustomPushService(manager, q);
+    }
+    return (custom_push_service != mDNSNULL);
+}
+
 mDNSexport void Querier_RegisterPathResolver(const uuid_t resolverUUID)
 {
     const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
@@ -668,6 +714,41 @@
     }
 }
 
+mDNSexport mdns_dns_service_id_t Querier_RegisterCustomPushDNSService(
+    const mdns_dns_push_service_definition_t push_service_definition)
+{
+    return Querier_RegisterCustomPushDNSServiceWithConnectionErrorHandler(push_service_definition, NULL, NULL);
+}
+
+mDNSexport mdns_dns_service_id_t Querier_RegisterCustomPushDNSServiceWithConnectionErrorHandler(
+    const mdns_dns_push_service_definition_t push_service_definition, const dispatch_queue_t connection_error_queue,
+    const mdns_event_handler_t connection_error_handler)
+{
+    mdns_dns_service_id_t ident = MDNS_DNS_SERVICE_INVALID_ID;
+    const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
+    if (manager)
+    {
+        OSStatus err = kNoErr;
+        ident = mdns_dns_service_manager_register_custom_push_service(manager, push_service_definition, 0,
+            connection_error_queue, connection_error_handler, &err);
+        if (err != kNoErr)
+        {
+            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_FAULT,
+                      "Failed to register custom push service - error: " PUB_OS_ERR, (long)err);
+        }
+    }
+    return ident;
+}
+
+mDNSexport void Querier_DeregisterCustomPushDNSService(const mdns_dns_service_id_t ident)
+{
+    const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
+    if (manager)
+    {
+        mdns_dns_service_manager_deregister_custom_push_service(manager, ident);
+    }
+}
+
 mDNSexport void Querier_RegisterDoHURI(const char *doh_uri, const char *domain)
 {
     const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
@@ -723,19 +804,20 @@
 
 #define kOrphanedQuerierMaxCount 10
 
+static const CFSetCallBacks gMDNSObjectSetCallbacks =
+{
+    .version         = 0,
+    .retain          = mdns_cf_callback_retain,
+    .release         = mdns_cf_callback_release,
+    .copyDescription = mdns_cf_callback_copy_description
+};
+
 mDNSlocal CFMutableSetRef _Querier_GetOrphanedQuerierSet(void)
 {
     static CFMutableSetRef sOrphanedQuerierSet = NULL;
     if (!sOrphanedQuerierSet)
     {
-        static const CFSetCallBacks sSetCallbacks =
-        {
-            .version         = 0,
-            .retain          = mdns_cf_callback_retain,
-            .release         = mdns_cf_callback_release,
-            .copyDescription = mdns_cf_callback_copy_description
-        };
-        sOrphanedQuerierSet = CFSetCreateMutable(kCFAllocatorDefault, 0, &sSetCallbacks);
+        sOrphanedQuerierSet = CFSetCreateMutable(kCFAllocatorDefault, 0, &gMDNSObjectSetCallbacks);
     }
     return sOrphanedQuerierSet;
 }
@@ -784,7 +866,13 @@
             }
             memcpy(&m->imsg.m, mdns_querier_get_response_ptr(querier), copyLen);
             const mDNSu8 *const end = ((mDNSu8 *)&m->imsg.m) + copyLen;
-            mDNSCoreReceiveForQuerier(m, &m->imsg.m, end, mdns_client_upcast(querier), dnsservice, mDNSInterface_Any);
+
+            mDNSInterfaceID interface = mDNSInterface_Any;
+            if (mdns_dns_service_has_local_purview(dnsservice))
+            {
+                interface = (mDNSInterfaceID)(uintptr_t)mdns_dns_service_get_interface_index(dnsservice);
+            }
+            mDNSCoreReceiveForQuerier(m, &m->imsg.m, end, mdns_client_upcast(querier), dnsservice, interface);
         }
     }
     const CFMutableSetRef set = _Querier_GetOrphanedQuerierSet();
@@ -873,6 +961,7 @@
                 case mdns_querier_result_type_null:
                 case mdns_querier_result_type_invalidation:
                 case mdns_querier_result_type_resolver_invalidation:
+                MDNS_COVERED_SWITCH_DEFAULT:
                     break;
             }
             if (startNewQuerier)
@@ -939,11 +1028,16 @@
     {
         mdns_client_t client;
     #if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
-        if (dns_question_enables_dns_push(q))
+        if (mdns_dns_service_uses_subscriber(q->dnsservice))
         {
             // We still need querier to discover the resolver if we are still in the discovery process.
             subscriber = mdns_dns_service_create_subscriber(q->dnsservice, NULL);
             mdns_require_quiet(subscriber, exit);
+
+            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
+                "[Q%u->Sub%llu] Created a subscriber for question", mDNSVal16(q->TargetQID),
+                mdns_subscriber_get_id(subscriber));
+
             client = mdns_client_upcast(subscriber);
         }
         else
@@ -959,9 +1053,36 @@
                 mdns_querier_set_checking_disabled(querier, true);
             }
         #endif
-            if (q->pid != 0)
+            // Set the identifier for the delegator relative to mDNSResponder, i.e., the identifier of the effective
+            // client.
+            //
+            // Order of preference:
+            //  1. q->DelegatorToken if available. This is the audit token of the immediate client's delegator, and
+            //     therefore the audit token of the effective client.
+            //  2. q->PeerToken if available and its PID is equal to a non-zero q->pid, which is the PID of the
+            //     effective client. In this case, the immediate client is the effective client, so use its audit token,
+            //     which is generally more informative than just a PID.
+            //  3. q->pid if it's non-zero. This is the PID of the effective client.
+            //  4. q->uuid. This is the UUID of the effective client.
+            if (0) {}
+        #if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+            else if (q->DelegatorToken)
             {
-                mdns_querier_set_delegator_pid(querier, q->pid);
+                mdns_querier_set_delegator_audit_token(querier, q->DelegatorToken);
+            }
+        #endif
+            else if (q->pid != 0)
+            {
+            #if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
+                if (mdns_audit_token_get_pid_null_safe(q->PeerToken, 0) == q->pid)
+                {
+                    mdns_querier_set_delegator_audit_token(querier, q->PeerToken);
+                }
+                else
+            #endif
+                {
+                    mdns_querier_set_delegator_pid(querier, q->pid);
+                }
             }
             else
             {
@@ -1013,6 +1134,9 @@
                         _Querier_HandleSubscriberInvalidation(subscriber);
                         mdns_release(subscriber);
                         break;
+
+                    MDNS_COVERED_SWITCH_DEFAULT:
+                        break;
                 }
                 KQueueUnlock("Subscriber event handler");
             });
@@ -1041,7 +1165,16 @@
 #endif
 
 exit:
-    q->ThisQInterval = (!q->dnsservice || q->client) ? FutureTime : mDNSPlatformOneSecond;
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
+    if (dns_question_uses_dns_polling(q))
+    {
+        // No need to change ThisQInterval because DNS polling uses constant interval value.
+    }
+    else
+#endif
+    {
+        q->ThisQInterval = (!q->dnsservice || q->client) ? FutureTime : mDNSPlatformOneSecond;
+    }
     q->LastQTime = m->timenow;
     SetNextQueryTime(m, q);
     mdns_forget(&querier);
@@ -1059,7 +1192,7 @@
     return count;
 }
 
-mDNSexport void Querier_ProcessDNSServiceChanges(void)
+mDNSexport void Querier_ProcessDNSServiceChanges(const mDNSBool updatePushQuestionServiceOnly)
 {
     mDNS *const m = &mDNSStorage;
     mDNSu32 slot;
@@ -1074,8 +1207,34 @@
     for (mDNSu32 i = 0; (i < count) && m->RestartQuestion; i++)
     {
         DNSQuestion *const q = m->RestartQuestion;
+        mDNSBool skipQuestion = mDNSfalse;
+#if MDNSRESPONDER_SUPPORTS(APPLE, TERMINUS_ASSISTED_UNICAST_DISCOVERY)
+        mDNSBool MDNSAlternativeModeOn = mDNSfalse;
+#endif
         if (mDNSOpaque16IsZero(q->TargetQID))
         {
+        #if MDNSRESPONDER_SUPPORTS(APPLE, TERMINUS_ASSISTED_UNICAST_DISCOVERY)
+            // Handles the case where mDNS query has a new discovery proxy service to use.
+            if (DNSQuestionIsEligibleForMDNSAlternativeService(q) &&
+                Querier_IsMDNSAlternativeServiceAvailableForQuestion(q))
+            {
+                q->TargetQID = mDNS_NewMessageID(m);
+                MDNSAlternativeModeOn = mDNStrue;
+            }
+            else
+        #endif
+            {
+                skipQuestion = mDNStrue;
+            }
+        }
+    #if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
+        else if (updatePushQuestionServiceOnly && !dns_question_uses_dns_push(q))
+        {
+            skipQuestion = mDNStrue;
+        }
+    #endif
+        if (skipQuestion)
+        {
             m->RestartQuestion = q->next;
             continue;
         }
@@ -1123,6 +1282,13 @@
                 }
             }
 #endif
+        #if MDNSRESPONDER_SUPPORTS(APPLE, TERMINUS_ASSISTED_UNICAST_DISCOVERY)
+            // Handles the case where a non-mDNS query loses its discovery proxy service (No new code needed to support
+            // this).
+            // If a .local unicast discovery query loses its preferred discovery service:
+            // DNS service changes from non-NULL to NULL, it will also be restarted here so that it can be downgrade to
+            // a normal mDNS query with TargetQID equal to 0.
+        #endif
             restart = mDNStrue;
         }
         else
@@ -1134,8 +1300,27 @@
         {
             if (!q->Suppressed)
             {
+            #if MDNSRESPONDER_SUPPORTS(APPLE, TERMINUS_ASSISTED_UNICAST_DISCOVERY)
+                // If we have decided to change the mDNS question to a non-mDNS question that uses mDNS alternative
+                // exclusive service for resolution, we need to deliver remove event for the records that get added
+                // as mDNS response, because we are switching to a different service.
+                // To ensure that the mDNS record matches the question, we need to change the question back to an mDNS
+                // question by making its QID zero.
+                const mDNSOpaque16 newQID = q->TargetQID;
+                if (MDNSAlternativeModeOn)
+                {
+                    q->TargetQID = zeroID;
+                }
+            #endif
                 CacheRecordRmvEventsForQuestion(m, q);
                 if (m->RestartQuestion == q) LocalRecordRmvEventsForQuestion(m, q);
+            #if MDNSRESPONDER_SUPPORTS(APPLE, TERMINUS_ASSISTED_UNICAST_DISCOVERY)
+                // After the function call, restore the original QID.
+                if (MDNSAlternativeModeOn)
+                {
+                    q->TargetQID = newQID;
+                }
+            #endif
             }
             if (m->RestartQuestion == q)
             {
@@ -1180,18 +1365,35 @@
         }
     }
 #endif
-    FORALL_CACHERECORDS(slot, cg, cr)
+    if (!updatePushQuestionServiceOnly)
     {
-        if (cr->resrec.InterfaceID) continue;
-        const mdns_dns_service_t dnsservice = mdns_cache_metadata_get_dns_service(cr->resrec.metadata);
-        if (!dnsservice || mdns_dns_service_is_defunct(dnsservice))
+        // If Querier_ProcessDNSServiceChanges is called with updatePushQuestionServiceOnly setting to true, then we
+        // skip scanning through all the resource records below by exiting early to avoid the unnecessary overhead.
+        FORALL_CACHERECORDS(slot, cg, cr)
         {
-            mdns_forget(&cr->resrec.metadata);
-            mDNS_PurgeCacheResourceRecord(m, cr);
+            if (cr->resrec.InterfaceID) continue;
+            const mdns_dns_service_t dnsservice = mdns_cache_metadata_get_dns_service(cr->resrec.metadata);
+            if (!dnsservice || mdns_dns_service_is_defunct(dnsservice))
+            {
+                mdns_forget(&cr->resrec.metadata);
+                mDNS_PurgeCacheResourceRecord(m, cr);
+            }
         }
     }
 }
 
+mDNSexport void Querier_ProcessDNSServiceChangesAsync(const mDNSBool updatePushQuestionServiceOnly)
+{
+    dispatch_async(_Querier_InternalQueue(),
+    ^{
+        KQueueLock();
+        mDNS_Lock(&mDNSStorage);
+        Querier_ProcessDNSServiceChanges(updatePushQuestionServiceOnly);
+        mDNS_Unlock(&mDNSStorage);
+        KQueueUnlock("Querier_ProcessDNSServiceChangesAsync");
+    });
+}
+
 mDNSexport DNSQuestion *Querier_GetDNSQuestion(const mdns_querier_t querier, mDNSBool *const outIsNew)
 {
     DNSQuestion *q;
@@ -1215,19 +1417,14 @@
     return q;
 }
 
-mDNSexport mDNSBool Querier_ResourceRecordIsAnswer(const ResourceRecord * const rr, const mdns_querier_t querier)
+mDNSexport mDNSBool Client_ResourceRecordIsAnswer(const ResourceRecord * const rr, const mdns_client_t client)
 {
     mDNSBool isAnswer;
-    const mDNSu16 qtype = mdns_querier_get_qtype(querier);
-    const mDNSu8 *const qname = mdns_querier_get_qname(querier);
+    const mDNSu16 type = mdns_client_get_type(client);
+    const mdns_domain_name_t name = mdns_client_get_name(client);
+    mdns_require_action_quiet(name, exit, isAnswer = mDNSfalse);
 
-    if (qname == mDNSNULL)
-    {
-        isAnswer = mDNSfalse;
-        goto exit;
-    }
-
-    if (rr->rrclass != mdns_querier_get_qclass(querier))
+    if (rr->rrclass != mdns_client_get_class(client))
     {
         isAnswer = mDNSfalse;
         goto exit;
@@ -1235,32 +1432,37 @@
 
     RRTypeAnswersQuestionTypeFlags flags = kRRTypeAnswersQuestionTypeFlagsNone;
 #if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
-    const mDNSBool requiresRRToValidate = mdns_querier_get_dnssec_ok(querier) && mdns_querier_get_checking_disabled(querier);
-    if (requiresRRToValidate)
+    const mdns_querier_t querier = mdns_querier_downcast(client);
+    if (querier)
     {
-        flags |= kRRTypeAnswersQuestionTypeFlagsRequiresDNSSECRRToValidate;
+        const mDNSBool requiresRRToValidate = mdns_querier_get_dnssec_ok(querier) &&
+            mdns_querier_get_checking_disabled(querier);
+        if (requiresRRToValidate)
+        {
+            flags |= kRRTypeAnswersQuestionTypeFlagsRequiresDNSSECRRToValidate;
+        }
     }
 #endif
 
-    isAnswer = RRTypeAnswersQuestionType(rr, qtype, flags);
+    isAnswer = RRTypeAnswersQuestionType(rr, type, flags);
     if (!isAnswer)
     {
         goto exit;
     }
 
-    isAnswer = SameDomainName(rr->name, (const domainname *)qname);
+    isAnswer = SameDomainName(rr->name, (const domainname *)mdns_domain_name_get_labels(name));
 
 exit:
     return isAnswer;
 }
 
-mDNSexport mDNSBool Querier_SameNameCacheRecordIsAnswer(const CacheRecord *const cr, const mdns_querier_t querier)
+mDNSexport mDNSBool Client_SameNameCacheRecordIsAnswer(const CacheRecord *const cr, const mdns_client_t client)
 {
     mDNSBool isAnswer;
     const ResourceRecord *const rr = &cr->resrec;
-    const mDNSu16 qtype = mdns_querier_get_qtype(querier);
+    const mDNSu16 qtype = mdns_client_get_type(client);
 
-    if (rr->rrclass != mdns_querier_get_qclass(querier))
+    if (rr->rrclass != mdns_client_get_class(client))
     {
         isAnswer = mDNSfalse;
         goto exit;
@@ -1268,12 +1470,18 @@
 
     RRTypeAnswersQuestionTypeFlags flags = kRRTypeAnswersQuestionTypeFlagsNone;
 #if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
-    const mDNSBool requiresRRToValidate = mdns_querier_get_dnssec_ok(querier) && mdns_querier_get_checking_disabled(querier);
-    if (requiresRRToValidate)
+    const mdns_querier_t querier = mdns_querier_downcast(client);
+    if (querier)
     {
-        flags |= kRRTypeAnswersQuestionTypeFlagsRequiresDNSSECRRToValidate;
+        const mDNSBool requiresRRToValidate = mdns_querier_get_dnssec_ok(querier) &&
+            mdns_querier_get_checking_disabled(querier);
+        if (requiresRRToValidate)
+        {
+            flags |= kRRTypeAnswersQuestionTypeFlagsRequiresDNSSECRRToValidate;
+        }
     }
 #endif
+
     isAnswer = RRTypeAnswersQuestionType(rr, qtype, flags);
 
 exit:
@@ -1293,7 +1501,7 @@
             const CFMutableSetRef set = _Querier_GetOrphanedQuerierSet();
             if (set && (CFSetGetCount(set) < kOrphanedQuerierMaxCount))
             {
-				CFSetAddValue(set, querier);
+                CFSetAddValue(set, querier);
                 LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
                     "[Q%u] Keeping orphaned querier for up to " StringifyExpansion(kOrphanedQuerierTimeLimitSecs) " seconds",
                     mdns_querier_get_user_id(querier));
@@ -1360,20 +1568,8 @@
     }
 }
 
-mDNSlocal mdns_dns_service_t _Querier_GetDotLocalPushService(const uint32_t ifIndex)
-{
-    mdns_dns_service_t service = NULL;
-    const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
-    if (manager)
-    {
-        service = _Querier_GetDNSPushDNSService(manager, &localdomain, ifIndex);
-    }
-
-    return service;
-}
-
-mDNSlocal void _Querier_RemoveRecord(const mdns_resource_record_t record, const mDNSInterfaceID interface,
-    const mDNSBool collective)
+mDNSlocal void _Querier_RemoveRecord(const mdns_resource_record_t record, const mdns_dns_service_t dnsService,
+    const mDNSInterfaceID interface, const mDNSBool collective)
 {
     const uint16_t rdlen = mdns_resource_record_get_rdata_length(record);
     require_quiet(rdlen <= MaximumRDSize, exit);
@@ -1381,12 +1577,14 @@
     mDNS *const m = &mDNSStorage;
     const mdns_domain_name_t name = mdns_resource_record_get_name(record);
     const domainname *const dname = (const domainname *)mdns_domain_name_get_labels(name);
+    mDNS_Lock(m);
     const CacheGroup *const cg = CacheGroupForName(m, DomainNameHashValue(dname), dname);
     if (cg)
     {
         const uint16_t type = mdns_resource_record_get_type(record);
         const uint16_t class = mdns_resource_record_get_class(record);
         const uint8_t *const rdata = mdns_resource_record_get_rdata_bytes_ptr(record);
+        const bool MDNSAlternative = mdns_dns_service_is_mdns_alternative(dnsService);
         for (CacheRecord *cr = cg->members; cr; cr = cr->next)
         {
             const ResourceRecord *const rr = &cr->resrec;
@@ -1394,6 +1592,14 @@
             {
                 continue;
             }
+            // If the service is an mDNS alternative service, then it is for unicast discovery, in which case,
+            // we do not care about the service match, because we treat it as an mDNS response.
+            // Otherwise, it is for general DNS push, in which case we treat it as a non-mDNS response so the
+            // service has to match.
+            if (!MDNSAlternative && mdns_cache_metadata_get_dns_service(rr->metadata) != dnsService)
+            {
+                continue;
+            }
             mDNSBool remove = mDNSfalse;
             if (collective)
             {
@@ -1436,19 +1642,22 @@
             }
         }
     }
+    mDNS_Unlock(m);
 
 exit:
     return;
 }
 
-mDNSlocal void _Querier_RemoveRecordSingle(const mdns_resource_record_t record, const mDNSInterfaceID interface)
+mDNSlocal void _Querier_RemoveRecordSingle(const mdns_resource_record_t record, const mdns_dns_service_t dnsService,
+    const mDNSInterfaceID interface)
 {
-	_Querier_RemoveRecord(record, interface, mDNSfalse);
+    _Querier_RemoveRecord(record, dnsService, interface, mDNSfalse);
 }
 
-mDNSlocal void _Querier_RemoveRecordCollective(const mdns_resource_record_t record, const mDNSInterfaceID interface)
+mDNSlocal void _Querier_RemoveRecordCollective(const mdns_resource_record_t record, const mdns_dns_service_t dnsService,
+    const mDNSInterfaceID interface)
 {
-	_Querier_RemoveRecord(record, interface, mDNStrue);
+    _Querier_RemoveRecord(record, dnsService, interface, mDNStrue);
 }
 
 #if !defined(kDNSPushChangeNotificationTTL_RemoveSingle)
@@ -1464,25 +1673,40 @@
     require_quiet(changeNotifications, exit);
 
     const mdns_dns_service_t dnsservice = (mdns_dns_service_t)mdns_client_get_context(subscriber);
-    const mDNSInterfaceID interface = (mDNSInterfaceID)(uintptr_t)mdns_dns_service_get_interface_index(dnsservice);
+    const mDNSInterfaceID interface = (mDNSInterfaceID)(uintptr_t)mdns_subscriber_get_interface_index(subscriber);
     mdns_cfarray_enumerate(changeNotifications,
     ^ bool (const mdns_resource_record_t record)
     {
         const uint32_t ttl = mdns_resource_record_get_ttl(record);
         if (ttl == kDNSPushChangeNotificationTTL_RemoveSingle)
         {
-            _Querier_RemoveRecordSingle(record, interface);
+            _Querier_RemoveRecordSingle(record, dnsservice, interface);
         }
         else if (ttl == kDNSPushChangeNotificationTTL_RemoveCollective)
         {
-            _Querier_RemoveRecordCollective(record, interface);
+            _Querier_RemoveRecordCollective(record, dnsservice, interface);
         }
         else
         {
+            if (!gMessageBuilder)
+            {
+                gMessageBuilder = mdns_message_builder_create();
+                mdns_require_return_value(gMessageBuilder, false);
+            }
             mdns_message_builder_reset(gMessageBuilder);
             mdns_message_builder_set_message_id(gMessageBuilder, 0);
             mdns_message_builder_set_qr_bit(gMessageBuilder, true);
             mdns_message_builder_set_aa_bit(gMessageBuilder, true);
+            if (dnsservice && !mdns_dns_service_is_mdns_alternative(dnsservice))
+            {
+                // If the subscriber is created for the non-mDNS unicast discovery, then the response should be
+                // processed by the uDNS code path of mDNSCoreReceiveResponse.
+                // If the subscriber is created for the mDNS-alternative unicast discovery, then the response should be
+                // process by the mDNS code path of mDNSCoreReceiveResponse. In which case, there is no need to add
+                // question section.
+                mdns_message_builder_set_question(gMessageBuilder, mdns_client_get_name(subscriber),
+                    mdns_client_get_type(subscriber), mdns_client_get_class(subscriber));
+            }
             mdns_message_builder_append_answer_record(gMessageBuilder, record);
             mDNS *const m = &mDNSStorage;
             uint8_t *const msgBuf = (uint8_t *)&m->imsg.m;
@@ -1504,6 +1728,7 @@
 mDNSlocal void _Querier_HandleSubscriberInvalidation(const mdns_subscriber_t subscriber)
 {
     mDNS *const m = &mDNSStorage;
+    mDNS_Lock(m);
     const mdns_domain_name_t name = mdns_client_get_name(subscriber);
     const domainname *const dname = (const domainname *)mdns_domain_name_get_labels(name);
     const CacheGroup *const cg = CacheGroupForName(m, DomainNameHashValue(dname), dname);
@@ -1514,42 +1739,234 @@
         {
             if (cr->DNSPushSubscribed && (mdns_cache_metadata_get_subscriber_id(cr->resrec.metadata) == ident))
             {
+                const ResourceRecord *const rr = &cr->resrec;
+
+                LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEBUG,
+                    "[Sub%llu] Removing record from the cache due to subscriber invalidation -- "
+                    "name: " PRI_DM_NAME ", type: " PUB_DNS_TYPE ", TTL: %us", ident, DM_NAME_PARAM(rr->name),
+                    DNS_TYPE_PARAM(rr->rrtype), rr->rroriginalttl);
+
+                mdns_cache_metadata_set_subscriber_id(rr->metadata, 0);
                 cr->DNSPushSubscribed = mDNSfalse;
-                SetNextCacheCheckTimeForRecord(m, cr);
+                mDNS_PurgeCacheResourceRecord(m, cr);
             }
         }
     }
+    mDNS_Unlock(m);
 }
 
-mDNSexport void Querier_HandleMDNSQuestion(DNSQuestion *const q)
+mDNSexport mDNSBool Querier_QuestionBelongsToSelf(const DNSQuestion *const q)
 {
-    mdns_subscriber_t subscriber = NULL;
-    mdns_domain_name_t qname = NULL;
-    const mDNSBool enabled = os_feature_enabled(mDNSResponder, dot_local_push);
-    require_quiet(enabled, exit);
-
-    if (mDNSOpaque16IsZero(q->TargetQID) && q->InterfaceID && !q->client)
+    if (q->ProxyQuestion)
     {
-        if (!q->dnsservice)
-        {
-            const uint32_t ifIndex = (uint32_t)((uintptr_t)q->InterfaceID);
-            q->dnsservice = _Querier_GetDotLocalPushService(ifIndex);
-            require_quiet(q->dnsservice, exit);
-        }
-        if (!gMessageBuilder)
-        {
-            gMessageBuilder = mdns_message_builder_create();
-            require_quiet(gMessageBuilder, exit);
-        }
-        subscriber = mdns_dns_service_create_subscriber(q->dnsservice, NULL);
-        require_quiet(subscriber, exit);
+        return mDNSfalse;
+    }
+    if (q->pid != 0)
+    {
+        return ((q->pid == _Querier_GetMyPID()) ? mDNStrue : mDNSfalse);
+    }
+    else
+    {
+        return ((uuid_compare(q->uuid, _Querier_GetMyUUID()) == 0) ? mDNStrue : mDNSfalse);
+    }
+}
 
-        qname = mdns_domain_name_create_with_labels(q->qname.c, NULL);
-        require_quiet(qname, exit);
+mDNSlocal mdns_dns_service_id_t
+_Querier_DNSServiceRegistrationStartHandler(const mdns_any_dns_service_definition_t definition,
+    const dispatch_queue_t connection_error_queue, const mdns_event_handler_t connection_error_handler)
+{
+    KQueueLock();
+    mdns_dns_service_id_t ident = MDNS_DNS_SERVICE_INVALID_ID;
+    mdns_dns_service_definition_t do53Definition = NULL;
+    mdns_dns_push_service_definition_t pushDefinition = NULL;
+
+    if ((do53Definition = mdns_dns_service_definition_downcast(definition)) != NULL) {
+        ident = Querier_RegisterNativeDNSService(do53Definition);
+    } else if ((pushDefinition = mdns_dns_push_service_definition_downcast(definition)) != NULL) {
+        ident = Querier_RegisterCustomPushDNSServiceWithConnectionErrorHandler(pushDefinition, connection_error_queue,
+            connection_error_handler);
+    }
+
+    KQueueUnlock("DNS service registration start handler");
+    return ident;
+}
+
+mDNSlocal void
+_Querier_DNSServiceRegistrationStopHandler(const mdns_dns_service_id_t ident)
+{
+    KQueueLock();
+    Querier_DeregisterNativeDNSService(ident);
+    Querier_DeregisterCustomPushDNSService(ident);
+    KQueueUnlock("DNS service registration stop handler");
+}
+
+const struct mrcs_server_dns_service_registration_handlers_s kMRCSServerDNSServiceRegistrationHandlers =
+{
+    .start = _Querier_DNSServiceRegistrationStartHandler,
+    .stop = _Querier_DNSServiceRegistrationStopHandler,
+};
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, DISCOVERY_PROXY_CLIENT)
+DNSQuestion DPCBrowse;
+
+mDNSexport mDNSBool DPCFeatureEnabled(void)
+{
+    static dispatch_once_t sOnce = 0;
+    static mDNSBool sEnabled = mDNSfalse;
+    dispatch_once(&sOnce,
+    ^{
+        sEnabled = os_feature_enabled(mDNSResponder, discovery_proxy_client);
+    });
+    return sEnabled;
+}
+
+mDNSlocal mDNSBool _DPCQuestionIsEligible(const DNSQuestion *const q)
+{
+    return (mDNSOpaque16IsZero(q->TargetQID) && ActiveQuestion(q));
+}
+
+static CFMutableDictionaryRef gDPCPushServers = mDNSNULL;
+
+mDNSlocal mdns_push_server_t _DPCGetPushServer(const mDNSInterfaceID interface)
+{
+    mdns_push_server_t server = mDNSNULL;
+    mdns_require_quiet(gDPCPushServers, exit);
+
+    server = (mdns_push_server_t)CFDictionaryGetValue(gDPCPushServers, interface);
+
+exit:
+    return server;
+}
+
+mDNSlocal mdns_subscriber_t _DPCQuestionGetSubscriber(const DNSQuestion *const q, const mDNSInterfaceID interface)
+{
+    __block mdns_subscriber_t subscriber = mDNSNULL;
+    mdns_require_quiet(q->DPSubscribers, exit);
+
+    mdns_cfset_enumerate(q->DPSubscribers,
+    ^ bool (const mdns_subscriber_t candidate)
+    {
+        const uintptr_t ifIndex = mdns_subscriber_get_interface_index(candidate);
+        if (((mDNSInterfaceID)ifIndex) == interface)
+        {
+            subscriber = candidate;
+        }
+        const bool proceed = !subscriber;
+        return proceed;
+    });
+
+exit:
+    return subscriber;
+}
+
+static CFMutableDictionaryRef gDPCSubscriberRegistries = mDNSNULL;
+
+// CFDictionary key callbacks for using mDNSInterfaceIDs as keys. Since these aren't references to objects, there are no
+// retains, releases, or other special functions. Note that a NULL equals function means that the dictionary will use
+// plain pointer equality to compare keys, which is appropriate for mDNSInterfaceIDs.
+static const CFDictionaryKeyCallBacks gInterfaceIDDictionaryKeyCallbacks =
+{
+    .version         = 0,
+    .retain          = mDNSNULL,
+    .release         = mDNSNULL,
+    .copyDescription = mDNSNULL,
+    .equal           = mDNSNULL,
+    .hash            = mDNSNULL
+};
+
+static const CFBagCallBacks gMDNSObjectBagCallbacks =
+{
+    .version         = 0,
+    .retain          = mdns_cf_callback_retain,
+    .release         = mdns_cf_callback_release,
+    .copyDescription = mdns_cf_callback_copy_description
+};
+
+mDNSlocal CFMutableBagRef _DPCGetSubscriberRegistryEx(const mDNSInterfaceID interface, const mDNSBool createIfAbsent)
+{
+    CFMutableBagRef registry = mDNSNULL;
+    if (!gDPCSubscriberRegistries && createIfAbsent)
+    {
+        gDPCSubscriberRegistries = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
+            &gInterfaceIDDictionaryKeyCallbacks, &kCFTypeDictionaryValueCallBacks);
+    }
+    mdns_require_quiet(gDPCSubscriberRegistries, exit);
+
+    registry = (CFMutableBagRef)CFDictionaryGetValue(gDPCSubscriberRegistries, interface);
+    if (!registry && createIfAbsent)
+    {
+        CFMutableBagRef newRegistry = CFBagCreateMutable(kCFAllocatorDefault, 0, &gMDNSObjectBagCallbacks);
+        mdns_require_quiet(newRegistry, exit);
+
+        CFDictionarySetValue(gDPCSubscriberRegistries, interface, newRegistry);
+        registry = newRegistry;
+        CFForget(&newRegistry);
+    }
+
+exit:
+    return registry;
+}
+
+mDNSlocal CFMutableBagRef _DPCGetSubscriberRegistryCreateIfAbsent(const mDNSInterfaceID interface)
+{
+    return _DPCGetSubscriberRegistryEx(interface, mDNStrue);
+}
+
+mDNSlocal CFMutableBagRef _DPCGetSubscriberRegistry(const mDNSInterfaceID interface)
+{
+    return _DPCGetSubscriberRegistryEx(interface, mDNSfalse);
+}
+
+mDNSlocal mdns_subscriber_t _DPCGetRegisteredSubscriber(const mDNSInterfaceID interface, const mDNSu8 *const name,
+    const mDNSu16 type, const mDNSu16 class)
+{
+    __block mdns_subscriber_t subscriber = mDNSNULL;
+    const CFBagRef registry = _DPCGetSubscriberRegistry(interface);
+    mdns_require_quiet(registry, exit);
+
+    mdns_cfbag_enumerate(registry,
+    ^ bool (const mdns_subscriber_t candidate)
+    {
+        if (mdns_subscriber_match(candidate, name, type, class))
+        {
+            subscriber = candidate;
+        }
+        const bool proceed = !subscriber;
+        return proceed;
+    });
+
+exit:
+    return subscriber;
+}
+
+mDNSlocal mDNSBool _DPCSubscribe(DNSQuestion *const q, const mDNSInterfaceID interface)
+{
+    __block mdns_subscriber_t subscriber = mDNSNULL;
+    mdns_domain_name_t qname = mDNSNULL;
+    const mdns_push_server_t server = _DPCGetPushServer(interface);
+    mdns_require_quiet(server, exit);
+
+    subscriber = _DPCQuestionGetSubscriber(q, interface);
+    mdns_require_quiet(!subscriber, exit);
+
+    if (!q->DPSubscribers)
+    {
+        q->DPSubscribers = CFSetCreateMutable(kCFAllocatorDefault, 0, &gMDNSObjectSetCallbacks);
+        mdns_require_quiet(q->DPSubscribers, exit);
+    }
+    const CFMutableBagRef registry = _DPCGetSubscriberRegistryCreateIfAbsent(interface);
+    mdns_require_quiet(registry, exit);
+
+    subscriber = _DPCGetRegisteredSubscriber(interface, q->qname.c, q->qtype, q->qclass);
+    if (!subscriber)
+    {
+        qname = mdns_domain_name_create_with_labels(q->qname.c, mDNSNULL);
+        mdns_require_quiet(qname, exit);
+
+        subscriber = mdns_push_server_create_subscriber(server, mDNSNULL);
+        mdns_require_quiet(subscriber, exit);
 
         mdns_client_set_query(subscriber, qname, q->qtype, q->qclass);
-        mdns_client_set_context(subscriber, q->dnsservice);
-        mdns_client_set_context_finalizer(subscriber, mdns_object_context_finalizer);
         mdns_client_set_queue(subscriber, _Querier_InternalQueue());
         mdns_retain(subscriber);
         mdns_subscriber_set_event_handler(subscriber,
@@ -1566,15 +1983,242 @@
                     _Querier_HandleSubscriberInvalidation(subscriber);
                     mdns_release(subscriber);
                     break;
+
+                MDNS_COVERED_SWITCH_DEFAULT:
+                    break;
             }
             KQueueUnlock("Subscriber event handler");
         });
-        mdns_client_replace(&q->client, subscriber);
-        mdns_client_activate(q->client);
+        mdns_client_activate(subscriber);
+    }
+    CFSetAddValue(q->DPSubscribers, subscriber);
+    CFBagAddValue(registry, subscriber);
+
+exit:
+    mdns_forget(&qname);
+    return (subscriber != mDNSNULL);
+}
+
+mDNSexport void DPCHandleNewQuestion(DNSQuestion *const q)
+{
+    const mDNSBool enabled = DPCFeatureEnabled();
+    mdns_require_quiet(enabled, exit);
+
+    const mDNSBool eligible = _DPCQuestionIsEligible(q);
+    mdns_require_quiet(eligible, exit);
+
+    if (q->InterfaceID == mDNSInterface_Any)
+    {
+        if (gDPCPushServers)
+        {
+            mdns_cfdictionary_apply(gDPCPushServers,
+            ^ bool (const void *const key, __unused const void *const value)
+            {
+                const mDNSInterfaceID interface = key;
+                _DPCSubscribe(q, interface);
+                return true;
+            });
+        }
+    }
+    else
+    {
+        _DPCSubscribe(q, q->InterfaceID);
     }
 
 exit:
-    mdns_forget(&subscriber);
-    mdns_forget(&qname);
+    return;
 }
+
+mDNSlocal mDNSBool _DPCQuestionHasSubscriber(const DNSQuestion *const q, const mDNSInterfaceID interface)
+{
+    const mdns_subscriber_t subscriber = _DPCQuestionGetSubscriber(q, interface);
+    return (subscriber != mDNSNULL);
+}
+
+mDNSexport mDNSBool DPCSuppressMDNSQuery(const DNSQuestion *const q, const mDNSInterfaceID interface)
+{
+    mDNSBool suppress = mDNSfalse;
+    const mDNSBool enabled = DPCFeatureEnabled();
+    mdns_require_quiet(enabled, exit);
+
+   	if (_DPCQuestionHasSubscriber(q, interface))
+    {
+        suppress = mDNStrue;
+    }
+
+exit:
+    return suppress;
+}
+
+mDNSexport mDNSBool DPCHaveSubscriberForRecord(const mDNSInterfaceID interface, const domainname *const name,
+    const mDNSu16 type, const mDNSu16 class)
+{
+    mDNSBool result = mDNSfalse;
+    const mDNSBool enabled = DPCFeatureEnabled();
+    mdns_require_quiet(enabled, exit);
+
+    const mdns_subscriber_t subscriber = _DPCGetRegisteredSubscriber(interface, name->c, type, class);
+    result = (subscriber != mDNSNULL);
+
+exit:
+    return result;
+}
+
+mDNSlocal void _DPDeregisterSubscriber(const mdns_subscriber_t subscriber)
+{
+    const uintptr_t ifIndex = mdns_subscriber_get_interface_index(subscriber);
+    const CFMutableBagRef registry = _DPCGetSubscriberRegistry((mDNSInterfaceID)ifIndex);
+    mdns_require_quiet(registry, exit);
+
+    const CFIndex count = CFBagGetCountOfValue(registry, subscriber);
+    mdns_require_quiet(count > 0, exit);
+
+    // Remove a subscriber instance from the registry. If the subscriber instance being removed was the last instance
+    // of the subscriber, then invalidate the subscriber since it's no longer being used.
+    CFBagRemoveValue(registry, subscriber);
+    if (count == 1)
+    {
+        mdns_client_invalidate(subscriber);
+    }
+
+exit:
+    return;
+}
+
+mDNSexport void DPCHandleStoppedDNSQuestion(DNSQuestion *const q)
+{
+    const mDNSBool enabled = DPCFeatureEnabled();
+    mdns_require_quiet(enabled, exit);
+    mdns_require_quiet(q->DPSubscribers, exit);
+
+    mdns_cfset_enumerate(q->DPSubscribers,
+    ^ bool (const mdns_subscriber_t subscriber)
+    {
+        _DPDeregisterSubscriber(subscriber);
+        return true;
+    });
+    CFSetRemoveAllValues(q->DPSubscribers);
+    CFForget(&q->DPSubscribers);
+
+exit:
+    return;
+}
+
+mDNSlocal void _DPCDisposeSubscriberRegistry(const mDNSInterfaceID interface)
+{
+    mdns_require_quiet(gDPCSubscriberRegistries, exit);
+
+    const CFMutableBagRef registry = (CFMutableBagRef)CFDictionaryGetValue(gDPCSubscriberRegistries, interface);
+    mdns_require_quiet(registry, exit);
+
+    mdns_cfbag_enumerate(registry,
+    ^ bool (const mdns_subscriber_t subscriber)
+    {
+        mdns_client_invalidate(subscriber);
+        return true;
+    });
+    CFBagRemoveAllValues(registry);
+    CFDictionaryRemoveValue(gDPCSubscriberRegistries, interface);
+
+exit:
+    return;
+}
+
+mDNSlocal void _DPCRemovePushServer(const mDNSInterfaceID interface)
+{
+    const mDNS *const m = &mDNSStorage;
+    for (DNSQuestion *q = m->Questions; q && (q != m->NewQuestions); q = q->next)
+    {
+        mdns_subscriber_t subscriber = _DPCQuestionGetSubscriber(q, interface);
+        if (subscriber)
+        {
+            CFSetRemoveValue(q->DPSubscribers, subscriber);
+        }
+    }
+    _DPCDisposeSubscriberRegistry(interface);
+    mdns_require_quiet(gDPCPushServers, exit);
+
+    const mdns_push_server_t server = (mdns_push_server_t)CFDictionaryGetValue(gDPCPushServers, interface);
+    if (server)
+    {
+        mdns_push_server_invalidate(server);
+        CFDictionaryRemoveValue(gDPCPushServers, interface);
+    }
+
+exit:
+    return;
+}
+
+static const CFDictionaryValueCallBacks gMDNSObjectDictionaryValueCallbacks =
+{
+    .version         = 0,
+    .retain          = mdns_cf_callback_retain,
+    .release         = mdns_cf_callback_release,
+    .copyDescription = mdns_cf_callback_copy_description
+};
+
+mDNSlocal void _DPCSetNewPushServer(const mDNSInterfaceID interface, mdns_domain_name_t srvName)
+{
+    mdns_push_server_t server = mDNSNULL;
+    _DPCRemovePushServer(interface);
+    if (!gDPCPushServers)
+    {
+        gDPCPushServers = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &gInterfaceIDDictionaryKeyCallbacks,
+            &gMDNSObjectDictionaryValueCallbacks);
+        mdns_require_quiet(gDPCPushServers, exit);
+    }
+    server = mdns_push_server_create();
+    mdns_require_quiet(server, exit);
+
+    mdns_push_server_set_srv_name(server, srvName);
+    mdns_push_server_activate(server);
+    CFDictionarySetValue(gDPCPushServers, interface, server);
+    const mDNS *const m = &mDNSStorage;
+    for (DNSQuestion *q = m->Questions; q && (q != m->NewQuestions); q = q->next)
+    {
+        if (_DPCQuestionIsEligible(q))
+        {
+            if ((q->InterfaceID == mDNSInterface_Any) || (q->InterfaceID == interface))
+            {
+                _DPCSubscribe(q, interface);
+            }
+        }
+    }
+
+exit:
+    mdns_forget(&server);
+}
+
+mDNSexport void DPCBrowseHandler(__unused mDNS *const m, __unused DNSQuestion *const q, const ResourceRecord *const answer,
+    const QC_result AddRecord)
+{
+    mdns_domain_name_t srvName = mDNSNULL;
+    const mDNSBool enabled = DPCFeatureEnabled();
+    mdns_require_quiet(enabled, exit);
+
+    if (AddRecord == QC_add)
+    {
+        const mDNSInterfaceID interface = answer->InterfaceID;
+        const mdns_push_server_t server = _DPCGetPushServer(interface);
+        if (!server)
+        {
+            srvName = mdns_domain_name_create_with_labels(answer->name->c, mDNSNULL);
+            mdns_require_quiet(srvName, exit);
+
+            _DPCSetNewPushServer(interface, srvName);
+        }
+    }
+
+exit:
+    mdns_forget(&srvName);
+}
+
+mDNSexport void DPCHandleInterfaceDown(const mDNSInterfaceID interface)
+{
+    const mDNSBool enabled = DPCFeatureEnabled();
+    mdns_require_return(enabled);
+
+    _DPCRemovePushServer(interface);
+}
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, DISCOVERY_PROXY_CLIENT)
 #endif // MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
diff --git a/mDNSMacOSX/QuerierSupport.h b/mDNSMacOSX/QuerierSupport.h
index 310f2f0..85f5595 100644
--- a/mDNSMacOSX/QuerierSupport.h
+++ b/mDNSMacOSX/QuerierSupport.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2019-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
 #define __QUERIER_SUPPORT_H__
 
 #include "mDNSEmbeddedAPI.h"
+#include "mrcs_server.h"
 #include <mdns/private.h>
 
 // Threshold value for problematic QTYPE workaround.
@@ -27,22 +28,48 @@
 extern void Querier_SetDNSServiceForQuestion(DNSQuestion *q);
 extern void Querier_ApplyDNSConfig(const dns_config_t *config);
 extern void Querier_HandleUnicastQuestion(DNSQuestion *q);
-extern void Querier_ProcessDNSServiceChanges(void);
+extern void Querier_ProcessDNSServiceChanges(mDNSBool updatePushQuestionServiceOnly);
+extern void Querier_ProcessDNSServiceChangesAsync(mDNSBool updatePushQuestionServiceOnly);
 extern void Querier_RegisterPathResolver(const uuid_t resolverUUID);
 extern mdns_dns_service_id_t Querier_RegisterCustomDNSService(xpc_object_t resolverConfigDict);
 extern mdns_dns_service_id_t Querier_RegisterCustomDNSServiceWithPListData(const uint8_t *dataPtr, size_t dataLen);
 extern void Querier_DeregisterCustomDNSService(mdns_dns_service_id_t ident);
 extern mdns_dns_service_id_t Querier_RegisterNativeDNSService(mdns_dns_service_definition_t dns_service_definition);
 extern void Querier_DeregisterNativeDNSService(mdns_dns_service_id_t ident);
+extern mdns_dns_service_id_t Querier_RegisterCustomPushDNSService(
+	mdns_dns_push_service_definition_t dns_service_definition);
+extern mdns_dns_service_id_t Querier_RegisterCustomPushDNSServiceWithConnectionErrorHandler(
+	mdns_dns_push_service_definition_t push_service_definition, dispatch_queue_t connection_error_queue,
+	mdns_event_handler_t connection_error_handler);
+extern void Querier_DeregisterCustomPushDNSService(mdns_dns_service_id_t ident);
+
 extern DNSQuestion *Querier_GetDNSQuestion(mdns_querier_t querier, mDNSBool *outIsNew);
-extern mDNSBool Querier_ResourceRecordIsAnswer(const ResourceRecord *rr, mdns_querier_t querier);
-extern mDNSBool Querier_SameNameCacheRecordIsAnswer(const CacheRecord *cr, mdns_querier_t querier);
+extern mDNSBool Client_ResourceRecordIsAnswer(const ResourceRecord *rr, mdns_client_t client);
+extern mDNSBool Client_SameNameCacheRecordIsAnswer(const CacheRecord *cr, mdns_client_t client);
 extern void Querier_HandleStoppedDNSQuestion(DNSQuestion *q);
 extern void Querier_RegisterDoHURI(const char *doh_uri, const char *domain);
 extern mdns_client_t Querier_HandlePreCNAMERestart(DNSQuestion *q);
 extern void Querier_HandlePostCNAMERestart(DNSQuestion *q, mdns_client_t client);
 extern void Querier_HandleSleep(void);
 extern void Querier_HandleWake(void);
-extern void Querier_HandleMDNSQuestion(DNSQuestion *const q);
+extern mDNSBool Querier_QuestionBelongsToSelf(const DNSQuestion *q);
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, TERMINUS_ASSISTED_UNICAST_DISCOVERY)
+extern mDNSBool Querier_IsMDNSAlternativeServiceAvailableForQuestion(const DNSQuestion *q);
+#endif
+extern mDNSBool Querier_IsCustomPushServiceAvailableForQuestion(const DNSQuestion *q);
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, DISCOVERY_PROXY_CLIENT)
+extern DNSQuestion DPCBrowse;
+extern mDNSBool DPCFeatureEnabled(void);
+extern void DPCHandleNewQuestion(DNSQuestion *q);
+extern mDNSBool DPCSuppressMDNSQuery(const DNSQuestion *q, mDNSInterfaceID interface);
+extern mDNSBool DPCHaveSubscriberForRecord(mDNSInterfaceID interface, const domainname *name, mDNSu16 type, mDNSu16 class_);
+extern void DPCHandleStoppedDNSQuestion(DNSQuestion *q);
+extern void DPCBrowseHandler(mDNS *m, DNSQuestion *q, const ResourceRecord *answer, QC_result AddRecord);
+extern void DPCHandleInterfaceDown(mDNSInterfaceID interface);
+#endif
+
+extern const struct mrcs_server_dns_service_registration_handlers_s kMRCSServerDNSServiceRegistrationHandlers;
 
 #endif  // __QUERIER_SUPPORT_H__
diff --git a/mDNSMacOSX/Scripts/bonjour-mcast-diagnose b/mDNSMacOSX/Scripts/bonjour-mcast-diagnose
index e191d7e..def02a8 100755
--- a/mDNSMacOSX/Scripts/bonjour-mcast-diagnose
+++ b/mDNSMacOSX/Scripts/bonjour-mcast-diagnose
@@ -1,11 +1,11 @@
 #! /bin/bash
 #
-#	Copyright (c) 2017-2021 Apple Inc. All rights reserved.
+#	Copyright (c) 2017-2023 Apple Inc. All rights reserved.
 #
 #	This script is currently for Apple Internal use only.
 #
 
-declare -r version=1.9
+declare -r version=1.10
 declare -r script=${BASH_SOURCE[0]}
 declare -r dnssdutil=${dnssdutil:-dnssdutil}
 
@@ -259,7 +259,7 @@
 RunMulticastTests()
 {
 	local -r interfaces=( $( ifconfig -l -u ) )
-	local -r skipPrefixes=( ap awdl bridge ipsec llw nan p2p pdp_ip pktap UDC utun )
+	local -r skipPrefixes=( awdl bridge ipsec llw nan p2p pdp_ip pktap UDC utun )
 	local -a pids
 	local ifname
 	local skip
diff --git a/mDNSMacOSX/Scripts/network-isolate-device b/mDNSMacOSX/Scripts/network-isolate-device
new file mode 100755
index 0000000..99123c7
--- /dev/null
+++ b/mDNSMacOSX/Scripts/network-isolate-device
@@ -0,0 +1,146 @@
+#! /bin/bash
+#
+#	Copyright (c) 2024 Apple Inc. All rights reserved.
+#
+#	This script is for Apple Internal use only.
+#
+
+declare -r version=1.0
+declare -r script=${BASH_SOURCE[0]}
+
+#============================================================================================================================
+#	PrintUsage
+#============================================================================================================================
+
+PrintUsage()
+{
+	echo ""
+	echo "Attempts to disable Wi-Fi and Ethernet interfaces so that tests from mDNSResponder's presubmission test suite"
+	echo "that are sensitive to network state changes and/or network activity can run without potential disruptions."
+	echo ""
+	echo "Usage: $( basename "${script}" ) [options]"
+	echo ""
+	echo "Options:"
+	echo "    -V    Display version of this script and exit."
+	echo ""
+}
+
+#============================================================================================================================
+#	LogOut
+#============================================================================================================================
+
+LogOut()
+{
+	echo "$( date '+%Y-%m-%d %H:%M:%S%z' ): $*"
+}
+
+#============================================================================================================================
+#	ErrQuit
+#============================================================================================================================
+
+ErrQuit()
+{
+	LogOut "error: $*"
+	exit 1
+}
+
+#============================================================================================================================
+#	main
+#============================================================================================================================
+
+main()
+{
+	while getopts ":hV" option; do
+		case "${option}" in
+			h)
+				PrintUsage
+				exit 0
+				;;
+			V)
+				echo "$( basename "${script}" ) version ${version}"
+				exit 0
+				;;
+			:)
+				ErrQuit "option '${OPTARG}' requires an argument."
+				;;
+			*)
+				ErrQuit "unknown option '${OPTARG}'."
+				;;
+		esac
+	done
+	# Check if the networksetup command, which is a macOS-only command, is available.
+	if command -v networksetup &> /dev/null ; then
+		# Turn off Wi-Fi.
+		networksetup -setairportpower Wi-Fi off
+		# Attempt to disable Ethernet network services if we're running in BATS, which sets BATS=1 in the environment,
+		# and the Mac under test is connected to an NCM host, which means that the BATS test host is controlling the Mac
+		# under test via NCM as opposed to Ethernet. If the BATS test host is controlling the Mac under test via
+		# Ethernet, then disabling Ethernet would cause the BATS test host to lose control of the Mac under test.
+		local disable_ethernet=false
+		local -r ethernet_services=( 'Ethernet' 'Thunderbolt Ethernet' )
+		if [ "${BATS}" == "1" ]; then
+			local connected_to_ncm_host=false
+			remotectl show ncm-host | grep -F -q 'State: connected'
+			if [ $? -eq 0 ]; then
+				connected_to_ncm_host=true
+			fi
+			if "${connected_to_ncm_host}"; then
+				LogOut "Connected ncm-host was detected"
+				networksetup -listallnetworkservices | grep -F -x -q -f <( printf '%s\n' "${ethernet_services[@]}" )
+				if [ $? -eq 0 ]; then
+					LogOut "Found Ethernet network services, will attempt to disable them"
+					disable_ethernet=true
+				else
+					LogOut "No Ethernet network services were found"
+				fi
+			else
+				LogOut "No connected ncm-host was detected, will not attempt to disable any Ethernet network services"
+			fi
+		fi
+		if "${disable_ethernet}"; then
+			local -r network_location=mDNSResponderTesting
+			networksetup -listlocations | grep -F -x -q "${network_location}"
+			if [ $? -eq 0 ]; then
+				LogOut "Network location '${network_location}' already exists"
+			else
+				LogOut "Network location '${network_location}' doesn't exist, will attempt to create it"
+				networksetup -createlocation "${network_location}" populate
+				if [ $? -eq 0 ]; then
+					LogOut "Created network location '${network_location}'"
+				else
+					ErrQuit "Failed to create network location"
+				fi
+			fi
+			networksetup -switchtolocation "${network_location}" > /dev/null
+			if [ $? -eq 0 ]; then
+				LogOut "Switched to network location '${network_location}'"
+			else
+				ErrQuit "Failed to switch network location to '${network_location}'"
+			fi
+			for network_service in "${ethernet_services[@]}"; do
+				networksetup -listallnetworkservices | grep -F -x -q "${network_service}"
+				if [ $? -eq 0 ]; then
+					networksetup -setnetworkserviceenabled "${network_service}" off
+					if [ $? -eq 0 ]; then
+						LogOut "Disabled network service '${network_service}'"
+					else
+						LogOut "Failed to disable network service '${network_service}'"
+					fi
+				fi
+			done
+		fi
+	# Otherwise, check if the mobilewifitool command is available.
+	elif command -v mobilewifitool &> /dev/null; then
+		# Turn off Wi-Fi.
+		mobilewifitool -- manager power 0
+		if [ $? -eq 0 ]; then
+			LogOut "Turned off Wi-Fi"
+		else
+			ErrQuit "Failed to turn off Wi-Fi"
+		fi
+	else
+		LogOut "Neither networksetup nor mobilewifitool were available"
+	fi
+}
+
+main "$@"
diff --git a/mDNSMacOSX/SettingsBundle/BonjourSettingsController.h b/mDNSMacOSX/SettingsBundle/BonjourSettingsController.h
index 0f3d95b..df18f49 100644
--- a/mDNSMacOSX/SettingsBundle/BonjourSettingsController.h
+++ b/mDNSMacOSX/SettingsBundle/BonjourSettingsController.h
@@ -15,7 +15,7 @@
  * limitations under the License.
  */
 
-#import <Preferences/Preferences.h>
+#import <Preferences/PSListItemsController.h>
 
 @interface BonjourSettingsController: PSListItemsController {
     
diff --git a/mDNSMacOSX/SettingsBundle/HostnameController.m b/mDNSMacOSX/SettingsBundle/HostnameController.m
index 94edc36..60b66d6 100644
--- a/mDNSMacOSX/SettingsBundle/HostnameController.m
+++ b/mDNSMacOSX/SettingsBundle/HostnameController.m
@@ -18,7 +18,7 @@
 #import "HostnameController.h"
 #import "BonjourSCStore.h"
 #import <AssertMacros.h>
-#import <Preferences/Preferences.h>
+#import <Preferences/PSEditableTableCell.h>
 
 #define LocalizedStringFromMyBundle(key, comment)     \
     NSLocalizedStringFromTableInBundle(key, @"Localizable", [NSBundle bundleForClass: [self class]], comment)
diff --git a/mDNSMacOSX/Tests/BATS Scripts/ps_filtering_rules.txt b/mDNSMacOSX/Tests/BATS Scripts/ps_filtering_rules.txt
new file mode 100644
index 0000000..3ff55a2
--- /dev/null
+++ b/mDNSMacOSX/Tests/BATS Scripts/ps_filtering_rules.txt
@@ -0,0 +1,4 @@
+alt_rosetta:1
+has_sec_transition:1
+add_env:SanitizersAllocationTraces=tagged
+binary_name:mDNSResponder
diff --git a/mDNSMacOSX/Tests/Unit Tests/DNSHeuristicsTest.m b/mDNSMacOSX/Tests/Unit Tests/DNSHeuristicsTest.m
index 59d4210..98e8a55 100644
--- a/mDNSMacOSX/Tests/Unit Tests/DNSHeuristicsTest.m
+++ b/mDNSMacOSX/Tests/Unit Tests/DNSHeuristicsTest.m
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020 Apple Inc. All rights reserved.
+ * Copyright (c) 2020-2023 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -24,17 +24,17 @@
 
 @implementation DNSHeuristicsTest
 
-#if (TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST)
+#if (TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST && !TARGET_OS_XR)
 - (void)testEmptyStateFailure {
     id mockHeuristics = OCMClassMock([DNSHeuristics class]);
-    OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg anyPointer]])).andReturn(@{});
-    OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg anyPointer] network:[OCMArg anyPointer] value:[OCMArg any]])).andReturn(YES);
-    OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
-    OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg any]])).andReturn(@{});
+    OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg any] value:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
 
     NSURL *url = [NSURL URLWithString:@"https://example.com"];
     XCTAssertTrue([DNSHeuristics reportResolutionFailure:url isTimeout:NO]);
-    OCMVerify(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]]));
+    OCMVerify(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]]));
 }
 
 - (void)testStateFailureUnderThreshold {
@@ -47,13 +47,13 @@
         DNSHeuristicsBurstCounterKey: [NSNumber numberWithUnsignedInteger:DNSHeuristicsDefaultBurstTokenBucketCapacity],
         DNSHeuristicsFilterFlagKey: @(NO),
     };
-    OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg anyPointer]])).andReturn(existingState);
-    OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg anyPointer] network:[OCMArg anyPointer] value:[OCMArg any]])).andReturn(YES);
-    OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
-    OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg any]])).andReturn(existingState);
+    OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg any] value:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
 
     XCTAssertTrue([DNSHeuristics reportResolutionFailure:url isTimeout:NO]);
-    OCMVerify(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]]));
+    OCMVerify(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]]));
 }
 
 - (void)testStateFailureOverThreshold {
@@ -66,13 +66,13 @@
         DNSHeuristicsBurstCounterKey: [NSNumber numberWithUnsignedInteger:DNSHeuristicsDefaultBurstTokenBucketCapacity],
         DNSHeuristicsFilterFlagKey: @(NO),
     };
-    OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg anyPointer]])).andReturn(existingState);
-    OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg anyPointer] network:[OCMArg anyPointer] value:[OCMArg any]])).andReturn(YES);
-    OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
-    OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg any]])).andReturn(existingState);
+    OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg any] value:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
 
     XCTAssertTrue([DNSHeuristics reportResolutionFailure:url isTimeout:NO]);
-    OCMVerify(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]]));
+    OCMVerify(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg any]]));
 }
 
 - (void)testStateFailureUnderThreshold_StickAfterFailure {
@@ -85,15 +85,15 @@
         DNSHeuristicsBurstCounterKey: [NSNumber numberWithUnsignedInteger:DNSHeuristicsDefaultBurstTokenBucketCapacity],
         DNSHeuristicsFilterFlagKey: @(YES),
     };
-    OCMStub(ClassMethod([mockHeuristics getNetworkFilteredFlag:[OCMArg anyPointer]])).andReturn(YES);
-    OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg anyPointer]])).andReturn(existingState);
-    OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg anyPointer] network:[OCMArg anyPointer] value:[OCMArg any]])).andReturn(YES);
-    OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
-    OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics getNetworkFilteredFlag:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg any]])).andReturn(existingState);
+    OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg any] value:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
     OCMStub(ClassMethod([mockHeuristics currentTimeMs])).andReturn(now + DNSHeuristicDefaultLongCounterTimeWindow * 2); // two days pass, we should reset
 
     XCTAssertTrue([DNSHeuristics reportResolutionFailure:url isTimeout:NO]);
-    OCMReject(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]]));
+    OCMReject(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]]));
 }
 
 - (void)testStateFailureUnderThreshold_ResetAfterSuccess {
@@ -105,15 +105,15 @@
         DNSHeuristicsBurstCounterKey: [NSNumber numberWithUnsignedInteger:DNSHeuristicsDefaultBurstTokenBucketCapacity],
         DNSHeuristicsFilterFlagKey: @(YES),
     };
-    OCMStub(ClassMethod([mockHeuristics getNetworkFilteredFlag:[OCMArg anyPointer]])).andReturn(YES);
-    OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg anyPointer]])).andReturn(existingState);
-    OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg anyPointer] network:[OCMArg anyPointer] value:[OCMArg any]])).andReturn(YES);
-    OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
-    OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics getNetworkFilteredFlag:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg any]])).andReturn(existingState);
+    OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg any] value:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
     OCMStub(ClassMethod([mockHeuristics currentTimeMs])).andReturn(now + DNSHeuristicDefaultLongCounterTimeWindow * 2); // two days pass, we should reset
 
     XCTAssertTrue([DNSHeuristics updateHeuristicState:YES isTimeout:NO]);
-    OCMVerify(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]]));
+    OCMVerify(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]]));
 }
 
 - (void)testStateFailureDrainTokenBucket_NoReset {
@@ -126,14 +126,14 @@
         DNSHeuristicsBurstCounterKey: [NSNumber numberWithInt:1],
         DNSHeuristicsFilterFlagKey: @(NO),
     };
-    OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg anyPointer]])).andReturn(existingState);
-    OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg anyPointer] network:[OCMArg anyPointer] value:[OCMArg any]])).andReturn(YES);
-    OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
-    OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg any]])).andReturn(existingState);
+    OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg any] value:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
     OCMStub(ClassMethod([mockHeuristics currentTimeMs])).andReturn(now + 1); // within the same epoch -- overflow
 
     XCTAssertTrue([DNSHeuristics reportResolutionFailure:url isTimeout:NO]);
-    OCMReject(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]]));
+    OCMReject(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]]));
 }
 
 - (void)testStateFailureDrainTokenBucket_Reset {
@@ -146,14 +146,14 @@
         DNSHeuristicsBurstCounterKey: [NSNumber numberWithInt:1],
         DNSHeuristicsFilterFlagKey: @(NO),
     };
-    OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg anyPointer]])).andReturn(existingState);
-    OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg anyPointer] network:[OCMArg anyPointer] value:[OCMArg any]])).andReturn(YES);
-    OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
-    OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg any]])).andReturn(existingState);
+    OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg any] value:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
     OCMStub(ClassMethod([mockHeuristics currentTimeMs])).andReturn(now + DNSHeuristicsDefaultBurstTokenBucketRefillTime + 1); // allow the bucket to replenish
 
     XCTAssertTrue([DNSHeuristics reportResolutionFailure:url isTimeout:NO]);
-    OCMReject(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]]));
+    OCMReject(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg any]]));
 }
 
 - (void)testStateFailureFilteredThenSuccessBeforeWindow {
@@ -165,15 +165,15 @@
 		DNSHeuristicsBurstCounterKey: [NSNumber numberWithUnsignedInteger:DNSHeuristicsDefaultBurstTokenBucketCapacity],
         DNSHeuristicsFilterFlagKey: @(YES),
     };
-    OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg anyPointer]])).andReturn(existingState);
-    OCMStub(ClassMethod([mockHeuristics getNetworkFilteredFlag:[OCMArg anyPointer]])).andReturn(YES);
-    OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg anyPointer] network:[OCMArg anyPointer] value:[OCMArg any]])).andReturn(YES);
-    OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
-    OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg any]])).andReturn(existingState);
+    OCMStub(ClassMethod([mockHeuristics getNetworkFilteredFlag:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg any] value:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
     OCMStub(ClassMethod([mockHeuristics currentTimeMs])).andReturn(now + DNSHeuristicsDefaultBurstTokenBucketRefillTime + 1); // allow the bucket to replenish
 
     XCTAssertTrue([DNSHeuristics updateHeuristicState:YES isTimeout:NO]);
-    OCMReject(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]]));
+    OCMReject(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]]));
 }
 
 - (void)testStateFailureFilteredThenSuccessAfterWindow {
@@ -185,17 +185,17 @@
         DNSHeuristicsBurstCounterKey: [NSNumber numberWithUnsignedInteger:DNSHeuristicsDefaultBurstTokenBucketCapacity],
         DNSHeuristicsFilterFlagKey: @(YES),
     };
-    OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg anyPointer]])).andReturn(existingState);
-    OCMStub(ClassMethod([mockHeuristics getNetworkFilteredFlag:[OCMArg anyPointer]])).andReturn(YES);
-    OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg anyPointer] network:[OCMArg anyPointer] value:[OCMArg any]])).andReturn(YES);
-    OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
-    OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics copyNetworkSettings:[OCMArg any]])).andReturn(existingState);
+    OCMStub(ClassMethod([mockHeuristics getNetworkFilteredFlag:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics setNetworkSettings:[OCMArg any] value:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
+    OCMStub(ClassMethod([mockHeuristics clearNetworkAsFiltered:[OCMArg any]])).andReturn(YES);
     OCMStub(ClassMethod([mockHeuristics currentTimeMs])).andReturn(now + DNSHeuristicsDefaultBurstTokenBucketRefillTime + 1); // allow the bucket to replenish
 
     XCTAssertTrue([DNSHeuristics updateHeuristicState:YES isTimeout:NO]);
-    OCMReject(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg anyPointer] network:[OCMArg anyPointer]]));
+    OCMReject(ClassMethod([mockHeuristics setNetworkAsFiltered:[OCMArg any]]));
 }
 
-#endif // TARGET_OS_IPHONE
+#endif // (TARGET_OS_IPHONE && !TARGET_OS_MACCATALYST && !TARGET_OS_XR)
 
 @end
diff --git a/mDNSMacOSX/Tests/mDNSResponder.plist b/mDNSMacOSX/Tests/mDNSResponder.plist
index ad5c5a9..0b61b05 100644
--- a/mDNSMacOSX/Tests/mDNSResponder.plist
+++ b/mDNSMacOSX/Tests/mDNSResponder.plist
@@ -15,6 +15,25 @@
 	<array>
 		<dict>
 			<key>TestName</key>
+			<string>Check Hardendened Heap Status</string>
+			<key>Description</key>
+			<string>Check the status of Hardened Heap (positive result will show Rosetta entries)</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/bin/footprint</string>
+				<string>mDNSResponder</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
 			<string>mDNSResponder Leaks</string>
 			<key>Description</key>
 			<string>Checks mDNSResponder for memory leaks.</string>
@@ -47,6 +66,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>gaiperf</string>
@@ -93,6 +114,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>gaiperf</string>
@@ -140,6 +163,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>gaiperf</string>
@@ -187,6 +212,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>dnsquery</string>
@@ -228,6 +255,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>fastrecovery</string>
@@ -269,6 +298,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>rcodes</string>
@@ -310,6 +341,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>gaiperf</string>
@@ -357,6 +390,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -413,6 +448,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -470,6 +507,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -526,6 +565,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -583,6 +624,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -639,6 +682,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -696,6 +741,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -751,6 +798,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -807,6 +856,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -862,6 +913,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -918,6 +971,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -973,6 +1028,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -1029,6 +1086,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -1090,6 +1149,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -1151,6 +1212,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -1212,6 +1275,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -1273,6 +1338,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -1330,6 +1397,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -1388,6 +1457,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -1445,6 +1516,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -1503,6 +1576,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -1560,6 +1635,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -1618,6 +1695,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -1674,6 +1753,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -1731,6 +1812,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -1787,6 +1870,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -1844,6 +1929,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -1900,6 +1987,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -1957,6 +2046,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -2019,6 +2110,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -2081,6 +2174,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -2143,6 +2238,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -2205,6 +2302,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -2250,7 +2349,7 @@
 			<key>TestName</key>
 			<string>mDNS Discovery 500-200-1 w/Memory Limit</string>
 			<key>Description</key>
-			<string>Tests mDNS discovery and resolution of 500 service instances with one 200-byte TXT record, one A record, and one AAAA record. Also, requires that mDNSResponder&apos;s heap memory growth doesn&apos;t exceed 3.1 MB. Note: This test comes after the &quot;mDNS Discovery 500-200-1&quot; test to ensure that mDNSResponder&apos;s record cache capacity doesn&apos;t have to grow during this test because we don&apos;t want increases to mDNSResponder&apos;s record cache capacity to be misconstrued as memory growth for client requests.</string>
+			<string>Tests mDNS discovery and resolution of 500 service instances with one 200-byte TXT record, one A record, and one AAAA record. Also, requires that mDNSResponder&apos;s heap memory growth doesn&apos;t exceed 3.2 MB. Note: This test comes after the &quot;mDNS Discovery 500-200-1&quot; test to ensure that mDNSResponder&apos;s record cache capacity doesn&apos;t have to grow during this test because we don&apos;t want increases to mDNSResponder&apos;s record cache capacity to be misconstrued as memory growth for client requests.</string>
 			<key>AsRoot</key>
 			<true/>
 			<key>RequiresWiFi</key>
@@ -2261,6 +2360,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>mdnsdiscovery</string>
@@ -2282,7 +2383,7 @@
 				<string>json</string>
 				<string>--flushCache</string>
 				<string>--memoryLimit</string>
-				<string>3250586</string>
+				<string>3355443</string>
 			</array>
 		</dict>
 		<dict>
@@ -2319,6 +2420,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>dotlocal</string>
@@ -2404,6 +2507,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>keepalive</string>
@@ -2445,6 +2550,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>probeconflicts</string>
@@ -2490,6 +2597,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>probeconflicts</string>
@@ -2535,6 +2644,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>probeconflicts</string>
@@ -2581,6 +2692,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>probeconflicts</string>
@@ -2671,6 +2784,8 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
 				<string>dnsproxy</string>
@@ -2699,11 +2814,11 @@
 		</dict>
 		<dict>
 			<key>TestName</key>
-			<string>DNS Proxy (Legacy SPI)</string>
+			<string>Record Registration</string>
 			<key>Description</key>
-			<string>Tests mDNSResponder&apos;s DNS proxy by sending it a variety of queries and verifying the responses. The queries are sent via UDP and TCP to the DNS proxy&apos;s IPV4 and IPv6 addresses. The DNS proxy is tested while it runs in different modes. Aside from the mode without a DNS64 prefix, the DNS proxy is tested while it runs with all of the valid DNS64 prefix lengths: 32-bit, 40-bit, 48-bit, 56-bit, 64-bit, and 96-bit. Uses the legacy client SPI to start the DNS proxies, i.e., DNSXEnableProxy() and DNSXEnableProxy64().</string>
+			<string>Tests the registration and deregistration of records.</string>
 			<key>AsRoot</key>
-			<true/>
+			<false/>
 			<key>RequiresWiFi</key>
 			<false/>
 			<key>Timeout</key>
@@ -2712,12 +2827,137 @@
 			<true/>
 			<key>Command</key>
 			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
 				<string>/usr/local/bin/dnssdutil</string>
 				<string>test</string>
-				<string>dnsproxy</string>
-				<string>--legacy</string>
-				<string>--format</string>
-				<string>json</string>
+				<string>record-registration</string>
+				<string>--interval</string>
+				<string>1500</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>60</integer>
+			<key>IgnoreOutput</key>
+			<true/>
+			<key>Command</key>
+			<array>
+				<string>/usr/bin/leaks</string>
+				<string>mDNSResponder</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>Record Cache Flush</string>
+			<key>Description</key>
+			<string>Tests the mrc_record_cache_flush SPI.</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<false/>
+			<key>Timeout</key>
+			<integer>300</integer>
+			<key>IgnoreOutput</key>
+			<true/>
+			<key>Command</key>
+			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
+				<string>/usr/local/bin/dnssdutil</string>
+				<string>test</string>
+				<string>record-cache-flush</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>60</integer>
+			<key>IgnoreOutput</key>
+			<true/>
+			<key>Command</key>
+			<array>
+				<string>/usr/bin/leaks</string>
+				<string>mDNSResponder</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>Resolver Override</string>
+			<key>Description</key>
+			<string>Verifies that a resolver override set with DNSServiceAttributeSetResolverOverride() is used when querying for records with DNSServiceQueryRecordWithAttribute().</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<false/>
+			<key>Timeout</key>
+			<integer>300</integer>
+			<key>IgnoreOutput</key>
+			<true/>
+			<key>Command</key>
+			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
+				<string>/usr/local/bin/dnssdutil</string>
+				<string>test</string>
+				<string>resolver-override</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>60</integer>
+			<key>IgnoreOutput</key>
+			<true/>
+			<key>Command</key>
+			<array>
+				<string>/usr/bin/leaks</string>
+				<string>mDNSResponder</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>Optimistic DNS</string>
+			<key>Description</key>
+			<string>Tests mDNSResponder&apos;s Optimistic DNS functionality, including its interoperability with search domains.</string>
+			<key>AsRoot</key>
+			<true/>
+			<key>RequiresWiFi</key>
+			<false/>
+			<key>Timeout</key>
+			<integer>300</integer>
+			<key>IgnoreOutput</key>
+			<true/>
+			<key>Command</key>
+			<array>
+				<string>/AppleInternal/Tests/mDNSResponder/network-isolate-device</string>
+				<string>;</string>
+				<string>/usr/local/bin/dnssdutil</string>
+				<string>test</string>
+				<string>optimisticDNS</string>
+				<string>--full</string>
 			</array>
 		</dict>
 		<dict>
@@ -3678,6 +3918,546 @@
 				<string>UTF8ValidatorTest</string>
 			</array>
 		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>Change Text Record</string>
+			<key>Description</key>
+			<string>SRP Client Changes Text Record, SRP Server Does DNSServiceUpdateRecord.</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<false/>
+			<key>Timeout</key>
+			<integer>15</integer>
+			<key>IgnoreOutput</key>
+			<false/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/srp-test-server</string>
+				<string>--test</string>
+				<string>change-text-record</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>Lease Expiry</string>
+			<key>Description</key>
+			<string>Verify service and host entries are removed when the lease provided by the SRP client expires.</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<false/>
+			<key>Timeout</key>
+			<integer>20</integer>
+			<key>IgnoreOutput</key>
+			<false/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/srp-test-server</string>
+				<string>--test</string>
+				<string>lease-expiry</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>Lease Renewal</string>
+			<key>Description</key>
+			<string>Verify service is renewed upon request from the client when the current lease is about to expire.</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<false/>
+			<key>Timeout</key>
+			<integer>30</integer>
+			<key>IgnoreOutput</key>
+			<false/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/srp-test-server</string>
+				<string>--test</string>
+				<string>lease-renewal</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>Single SRPL Update test</string>
+			<key>Description</key>
+			<string>
+				Test that when we generate a DNS Update message and deliver it through the SRP replication
+				input path, it is successfully registered.
+			</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<false/>
+			<key>Timeout</key>
+			<integer>20</integer>
+			<key>IgnoreOutput</key>
+			<false/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/srp-test-server</string>
+				<string>--test</string>
+				<string>single-srpl-update</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>Two-instance SRPL registration test</string>
+			<key>Description</key>
+			<string>
+				Test that when we generate two DNS Update message, each of which adds a single instance,
+				and deliver them together through the SRP replication input path, both are successfully registered,
+				and the host registration is only done once and not renewed.
+			</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<false/>
+			<key>Timeout</key>
+			<integer>20</integer>
+			<key>IgnoreOutput</key>
+			<false/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/srp-test-server</string>
+				<string>--test</string>
+				<string>srpl-two-instances</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>Two-instance SRPL registration + remove test</string>
+			<key>Description</key>
+			<string>
+				Test that when we generate two DNS Update message, the first of which adds two instances, and the
+				second of which removes the first instance, and then we deliver them together through the SRP replication
+				input path, the second is successfully registered, but the first is never registered, and the host registration
+				and second instance registration are done exactly once and not renewed.
+			</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<false/>
+			<key>Timeout</key>
+			<integer>20</integer>
+			<key>IgnoreOutput</key>
+			<false/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/srp-test-server</string>
+				<string>--test</string>
+				<string>srpl-two-instances-one-remove</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>Zero-instance SRPL registration test</string>
+			<key>Description</key>
+			<string> test that an SRP client registers a host with no instances through the primary SRP server, and the update message is delivered to the other SRP server through SRPL connection and then the host is registered on that SRP server.</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<false/>
+			<key>Timeout</key>
+			<integer>20</integer>
+			<key>IgnoreOutput</key>
+			<false/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/srp-test-server</string>
+				<string>--test</string>
+				<string>srpl-zero-instances-two-servers</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>SRPL lease time test</string>
+			<key>Description</key>
+			<string> test that lease time gets renewed as expected from replication.</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<false/>
+			<key>Timeout</key>
+			<integer>60</integer>
+			<key>IgnoreOutput</key>
+			<false/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/srp-test-server</string>
+				<string>--test</string>
+				<string>srpl-lease-time</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>SRPL cycle through peers test</string>
+			<key>Description</key>
+			<string> test that lease time is not accidentally extended because of replication.</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<false/>
+			<key>Timeout</key>
+			<integer>60</integer>
+			<key>IgnoreOutput</key>
+			<false/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/srp-test-server</string>
+				<string>--test</string>
+				<string>srpl-cycle-through-peers</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>SRPL update after remove test</string>
+			<key>Description</key>
+			<string> test that a stale update through replication does not re-add the host that has been removed by the actual device.</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<false/>
+			<key>Timeout</key>
+			<integer>60</integer>
+			<key>IgnoreOutput</key>
+			<false/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/srp-test-server</string>
+				<string>--test</string>
+				<string>srpl-update-after-remove</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>Two-instance SRPL registration + remove test with duplicate</string>
+			<key>Description</key>
+			<string>
+				The goal of this test is to generate two DNS Update messages, both for the
+				same host, each containing a new registration for a different instance, and
+				deliver it through the SRP replication input path. Having passed the update set once
+				we then deliver it a second time to see that it is correctly ignored.
+				VARIANT: test that if the two updates contain the same messages, the second set are ignored.
+			</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<false/>
+			<key>Timeout</key>
+			<integer>20</integer>
+			<key>IgnoreOutput</key>
+			<false/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/srp-test-server</string>
+				<string>--test</string>
+				<string>srpl-two-instances-dup</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>Two-instance SRPL registration + remove test with duplicate of first message only</string>
+			<key>Description</key>
+			<string>
+				The goal of this test is to generate two DNS Update messages, both for the
+				same host, each containing a new registration for a different instance, and
+				deliver it through the SRP replication input path. Having passed the update set once
+				we then deliver it a second time to see that it is correctly ignored.
+				VARIANT: test that if the second updates contain only the first message, it is ignored.
+			</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<false/>
+			<key>Timeout</key>
+			<integer>20</integer>
+			<key>IgnoreOutput</key>
+			<false/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/srp-test-server</string>
+				<string>--test</string>
+				<string>srpl-two-instances-dup-first</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>Two-instance SRPL registration + remove test with duplicate of last message only</string>
+			<key>Description</key>
+			<string>
+				The goal of this test is to generate two DNS Update messages, both for the
+				same host, each containing a new registration for a different instance, and
+				deliver it through the SRP replication input path. Having passed the update set once
+				we then deliver it a second time to see that it is correctly ignored.
+				VARIANT: test that if the second updates contain only the last message, it is ignored.
+			</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<false/>
+			<key>Timeout</key>
+			<integer>20</integer>
+			<key>IgnoreOutput</key>
+			<false/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/srp-test-server</string>
+				<string>--test</string>
+				<string>srpl-two-instances-dup-last</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>Two-instance SRPL registration + remove, then add a new instance plus dupes of the original messages</string>
+			<key>Description</key>
+			<string>
+				The goal of this test is to generate two DNS Update messages, both for the
+				same host, each containing a new registration for a different instance, and
+				deliver it through the SRP replication input path. Having passed the update set once
+				we then deliver it a second time to see that it is correctly ignored.
+				VARIANT: test that if the second update starts with a new third instance, it is added.
+			</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<false/>
+			<key>Timeout</key>
+			<integer>20</integer>
+			<key>IgnoreOutput</key>
+			<false/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/srp-test-server</string>
+				<string>--test</string>
+				<string>srpl-two-instances-dup-add-first</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>Two-instance SRPL registration + remove plus duplicates of these messages and a new instance add</string>
+			<key>Description</key>
+			<string>
+				The goal of this test is to generate two DNS Update messages, both for the
+				same host, each containing a new registration for a different instance, and
+				deliver it through the SRP replication input path. Having passed the update set once
+				we then deliver it a second time to see that it is correctly ignored.
+				VARIANT: test that if the second update ends with a new third instance, it is added.
+			</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<false/>
+			<key>Timeout</key>
+			<integer>20</integer>
+			<key>IgnoreOutput</key>
+			<false/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/srp-test-server</string>
+				<string>--test</string>
+				<string>srpl-two-instances-dup-add-last</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>Two-instance SRPL registration + remove with different keys</string>
+			<key>Description</key>
+			<string>
+				The goal of this test is to generate two DNS Update messages, both for the
+				same host, each containing a new registration for a different instance, and
+				deliver it through the SRP replication input path. Having passed the update set once
+				we then deliver it a second time to see that it is correctly ignored.
+				VARIANT: test that if the two updates are signed with different keys, they are rejected.
+			</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<false/>
+			<key>Timeout</key>
+			<integer>20</integer>
+			<key>IgnoreOutput</key>
+			<false/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/srp-test-server</string>
+				<string>--test</string>
+				<string>srpl-two-instances-dup-2keys</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>DNS Dangling Query test</string>
+			<key>Description</key>
+			<string>
+				The goal of this test is to generate two DNS Update messages, both for the
+				same host, each containing a new registration for a different instance, and
+				deliver it through the SRP replication input path. Having passed the update set once
+				we then deliver it a second time to see that it is correctly ignored.
+				VARIANT: test that if the two updates are signed with different keys, they are rejected.
+			</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<false/>
+			<key>Timeout</key>
+			<integer>20</integer>
+			<key>IgnoreOutput</key>
+			<false/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/srp-test-server</string>
+				<string>--test</string>
+				<string>dns-dangling-query</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>DNS local query</string>
+			<key>Description</key>
+			<string>
+				The goal of this test is to create DNS connection to the test server and attempt to
+				look up a name that goes through the local query path. If we get a response, the test
+				succeeded.
+			</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<false/>
+			<key>Timeout</key>
+			<integer>20</integer>
+			<key>IgnoreOutput</key>
+			<false/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/srp-test-server</string>
+				<string>--test</string>
+				<string>dns-mdns</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>DNS Push local query</string>
+			<key>Description</key>
+			<string>
+				The goal of this test is to create DNS Push connection to the test server and attempt to
+				look up a name that goes through the local query path. If we get a response, the test
+				succeeded.
+			</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<false/>
+			<key>Timeout</key>
+			<integer>20</integer>
+			<key>IgnoreOutput</key>
+			<false/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/srp-test-server</string>
+				<string>--test</string>
+				<string>dns-push-mdns</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>DNS Push server crash</string>
+			<key>Description</key>
+			<string>
+				The goal of this test is to create DNS Push connection to the test server and attempt to
+				look up a name that goes through the local (mDNS) query path. Once we have a result, we fake
+				an mDNSResponder crash and make sure the query is successfully restarted.
+			</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<false/>
+			<key>Timeout</key>
+			<integer>20</integer>
+			<key>IgnoreOutput</key>
+			<false/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/srp-test-server</string>
+				<string>--test</string>
+				<string>dns-push-crash</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>DNS two-question query</string>
+			<key>Description</key>
+			<string>
+			  The goal of this test is to create DSO connection to the test server and send a DNS query that
+			  looks up a name that goes through the hardwired query path. If we get a response, the test
+			  succeeded.
+			</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<false/>
+			<key>Timeout</key>
+			<integer>20</integer>
+			<key>IgnoreOutput</key>
+			<false/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/srp-test-server</string>
+				<string>--test</string>
+				<string>dns-two</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>Listener Longevity test</string>
+			<key>Description</key>
+			<string>
+				The goal of this test is to see that when we start the dnssd proxy UDP listener and deliver
+				an SRP registration to it, that the listener is not canceled after 30 seconds. This test is
+				in place because of rdar://124631151 (Unable to pair matter thread accessory ...) which was
+				cause by erroneously starting a tracker idle timeout when processing an incoming SRP
+				registration on port 53, which is where we receive anycast updates.  The test fails if
+				after 30 seconds the listener has been canceled.
+			</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<false/>
+			<key>Timeout</key>
+			<integer>60</integer>
+			<key>IgnoreOutput</key>
+			<false/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/srp-test-server</string>
+				<string>--test</string>
+				<string>listener-longevity</string>
+			</array>
+		</dict>
+		<dict>
+			<key>TestName</key>
+			<string>getifaddr test</string>
+			<key>Description</key>
+			<string>
+				This test checks to see that when the link-layer address on an interface is changed, we get a remove event
+				for the old address and an add event for the new address in ioloop_map_interface_addresses_here().
+			</string>
+			<key>AsRoot</key>
+			<false/>
+			<key>RequiresWiFi</key>
+			<false/>
+			<key>Timeout</key>
+			<integer>2</integer>
+			<key>IgnoreOutput</key>
+			<false/>
+			<key>Command</key>
+			<array>
+				<string>/usr/local/bin/srp-test-server</string>
+				<string>--test</string>
+				<string>ifaddrs</string>
+			</array>
+		</dict>
 	</array>
 </dict>
 </plist>
diff --git a/mDNSMacOSX/clientstub/dnssd_clientstub_apple.h b/mDNSMacOSX/clientstub/dnssd_clientstub_apple.h
index 530c9f0..89ffaf3 100644
--- a/mDNSMacOSX/clientstub/dnssd_clientstub_apple.h
+++ b/mDNSMacOSX/clientstub/dnssd_clientstub_apple.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020-2022 Apple Inc. All rights reserved.
+ * Copyright (c) 2020-2024 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -40,9 +40,6 @@
 get_required_tlv_length_for_defaults(xpc_object_t defaults);
 
 size_t
-get_required_tlv_length_for_service_attr(const DNSServiceAttribute *attr);
-
-size_t
 get_required_tlv_length_for_get_tracker_info(void);
 
 const uint8_t *
diff --git a/mDNSMacOSX/com.apple.mDNSResponder.plist b/mDNSMacOSX/com.apple.mDNSResponder.plist
index a9a01e8..2d71cc3 100644
--- a/mDNSMacOSX/com.apple.mDNSResponder.plist
+++ b/mDNSMacOSX/com.apple.mDNSResponder.plist
@@ -20,8 +20,6 @@
 		<true/>
 		<key>com.apple.mDNSResponder.control</key>
 		<true/>
-		<key>com.apple.mDNSResponder.dnsproxy</key>
-		<true/>
 		<key>com.apple.mDNSResponder.log_utility</key>
 		<true/>
 	</dict>
diff --git a/mDNSMacOSX/daemon.c b/mDNSMacOSX/daemon.c
index d03c1a9..cf888f1 100644
--- a/mDNSMacOSX/daemon.c
+++ b/mDNSMacOSX/daemon.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4; c-file-style: "bsd"; c-basic-offset: 4; fill-column: 108; indent-tabs-mode: nil -*-
  *
- * Copyright (c) 2002-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -41,9 +41,10 @@
 
 #include "uds_daemon.h"             // Interface to the server side implementation of dns_sd.h
 #include "xpc_services.h"
-#include "xpc_service_dns_proxy.h"
 #include "xpc_service_log_utility.h"
 #include "helper.h"
+#include "dnsproxy.h"
+#include "discovery_proxy.h"
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, ANALYTICS)
 #include "dnssd_analytics.h"
@@ -62,6 +63,10 @@
 #include "resolved_cache.h"
 #endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, UNICAST_ASSIST)
+#include "unicast_assist_cache.h"
+#endif
+
 #include <mdns/power.h>
 #include "mrcs_server.h"
 
@@ -206,14 +211,27 @@
             else
                 prevnew->c[0] = 0;
 #endif
+
+        #define LogRedactWithNameChangeKey(CATEGORY, LEVEL, KEY, FORMAT, ...)                           \
+            do {                                                                                        \
+                if ((key) == kmDNSComputerName)                                                         \
+                {                                                                                       \
+                    LogRedact(CATEGORY, LEVEL,                                                          \
+                        "mDNSPreferencesSetNames: changing computer name -- " FORMAT, ##__VA_ARGS__);   \
+                }                                                                                       \
+                else                                                                                    \
+                {                                                                                       \
+                    LogRedact(CATEGORY, LEVEL,                                                          \
+                        "mDNSPreferencesSetNames: changing local host name -- " FORMAT, ##__VA_ARGS__); \
+                }                                                                                       \
+            } while (0)
+
+            LogRedactWithNameChangeKey(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT, key,
+                "last change: " PRI_DM_LABEL " -> " PRI_DM_LABEL
+                ", current change: " PRI_DM_LABEL " -> " PRI_DM_LABEL, DM_LABEL_PARAM_SAFE(prevold),
+                DM_LABEL_PARAM_SAFE(prevnew), DM_LABEL_PARAM_SAFE(old), DM_LABEL_PARAM_SAFE(new));
             mDNSPreferencesSetName(key, old, new);
-        }
-        else
-        {
-            LogInfo("mDNSPreferencesSetNames not invoking helper %s %#s, %s %#s, old %#s, new %#s",
-                    (key == kmDNSComputerName ? "prevoldnicelabel" : "prevoldhostlabel"), prevold->c,
-                    (key == kmDNSComputerName ? "prevnewnicelabel" : "prevnewhostlabel"), prevnew->c,
-                    old->c, new->c);
+        #undef LogRedactWithNameChangeKey
         }
         break;
     default:
@@ -329,9 +347,9 @@
             return "Inter-(co)proc";
         case IFRTYPE_FUNCTIONAL_COMPANIONLINK:
             return "CompanionLink";
+        default:
+            return "Unrecognized";
     }
-
-    return "Unrecognized";
 }
 
 mDNSexport void dump_state_to_fd(int fd)
@@ -389,8 +407,10 @@
                           &i->ifinfo.ip, utc - i->LastSeen);
             else
             {
-                const CacheRecord *sps[3];
+                const CacheRecord *sps[3] = {mDNSNULL, mDNSNULL, mDNSNULL};
+            #if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
                 FindSPSInCache(&mDNSStorage, &i->ifinfo.NetWakeBrowse, sps);
+            #endif
                 LogToFD(fd, "%p %2ld, %p,  %s %-8.8s %.6a %.6a %s %s %s %s %s %s %-16.16s %#a",
                           i, i->ifinfo.InterfaceID, i->Registered,
                           i->sa_family == AF_INET ? "v4" : i->sa_family == AF_INET6 ? "v6" : "??", i->ifinfo.ifname, &i->ifinfo.MAC, &i->BSSID,
@@ -510,9 +530,13 @@
         LogToFD(fd, "%##s", mDNSStorage.FQDN.c);
     }
 
-    #if MDNSRESPONDER_SUPPORTS(APPLE, ANALYTICS)
-        dnssd_analytics_log(fd);
-    #endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, ANALYTICS)
+    dnssd_analytics_log(fd);
+#endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, UNICAST_ASSIST)
+    unicast_assist_cache_log(fd);
+#endif
 
     LogToFD(fd, "Date: %s", timestamp);
     LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "---- END STATE LOG ---- (" PUB_S ")", timestamp);
@@ -636,6 +660,12 @@
         LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "All mDNSResponder Debug Logging/Tracing Disabled (USR1/USR2/PROF)");
         UpdateDebugState();
         break;
+#if MDNSRESPONDER_SUPPORTS(APPLE, UNICAST_ASSIST)
+    case SIGWINCH:
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "SIGWINCH: Purge unicast assist cache");
+        unicast_assist_cache_purge();
+        break;
+#endif
 
     default: LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "SignalCallback: Unknown signal %d", msg_header->msgh_id); break;
     }
@@ -1011,6 +1041,7 @@
             if (m->p->IOPMConnection)   // If lightweight-wake capability is available, use that
             {
                 CFStringRef reasonStr;
+                mdns_clang_ignore_warning_begin(-Wswitch-default);
                 switch (reason)
                 {
                 case mDNSNextWakeReason_NATPortMappingRenewal:
@@ -1037,6 +1068,7 @@
                     reasonStr = CFSTR("unspecified");
                     break;
                 }
+                mdns_clang_ignore_warning_end();
                 CFDateRef WakeDate = CFDateCreate(NULL, CFAbsoluteTimeGetCurrent() + interval);
                 if (!WakeDate) LogMsg("ScheduleNextWake: CFDateCreate failed");
                 else
@@ -1098,9 +1130,11 @@
         }
 
         m->SleepState = SleepState_Sleeping;
+    #if !MDNSRESPONDER_SUPPORTS(APPLE, KEEP_INTERFACES_DURING_SLEEP)
 		// Clear our interface list to empty state, ready to go to sleep
 		// As a side effect of doing this, we'll also cancel any outstanding SPS Resolve calls that didn't complete
         mDNSMacOSXNetworkChanged();
+    #endif
     }
 
 #if TARGET_OS_OSX && defined(kIOPMAcknowledgmentOptionSystemCapabilityRequirements)
@@ -1226,6 +1260,17 @@
         LogMsg("SO_RCVLOWAT IPv6 %d error %d errno %d (%s)", k->sktv6, r, errno, strerror(errno));
 }
 
+mDNSlocal void MRCSServerInit(void)
+{
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    mrcs_server_set_dns_service_registration_handlers(&kMRCSServerDNSServiceRegistrationHandlers);
+#endif
+    mrcs_server_set_dns_proxy_handlers(&kMRCSServerDNSProxyHandlers);
+    mrcs_server_set_discovery_proxy_handlers(&kMRCSServerDiscoveryProxyHandlers);
+    mrcs_server_set_record_cache_handlers(&kMRCServerRecordCacheHandlers);
+    mrcs_server_activate();
+}
+
 mDNSlocal void * KQueueLoop(void *m_param)
 {
     mDNS            *m = m_param;
@@ -1242,7 +1287,10 @@
 #if MDNSRESPONDER_SUPPORTS(APPLE, DNSSD_XPC_SERVICE)
     dnssd_server_init();
 #endif
-    mrcs_server_init(&kMRCSServerHandlers);
+#if MDNSRESPONDER_SUPPORTS(APPLE, UNICAST_ASSIST)
+    unicast_assist_init();
+#endif
+    MRCSServerInit();
     pthread_mutex_lock(&PlatformStorage.BigMutex);
     LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "Starting time value 0x%08X (%d)", (mDNSu32)mDNSStorage.timenow_last, mDNSStorage.timenow_last);
 
@@ -1266,6 +1314,9 @@
 #if MDNSRESPONDER_SUPPORTS(APPLE, TRACKER_STATE)
         resolved_cache_idle();
 #endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, UNICAST_ASSIST)
+        unicast_assist_idle();
+#endif
 #if MDNSRESPONDER_SUPPORTS(COMMON, DNS_PUSH)
         mDNS_Lock(m);
         nextTimerEvent = dso_idle(m, m->timenow, nextTimerEvent);
@@ -1424,37 +1475,52 @@
 }
 
 
-extern int sandbox_init(const char *profile, uint64_t flags, char **errorbuf) __attribute__((weak_import));
-
-
 mDNSlocal void SandboxProcess(void)
 {
+#if MDNS_OS(macOS)
     // Invoke sandbox profile /usr/share/sandbox/mDNSResponder.sb
-#if defined(MDNS_NO_SANDBOX) && MDNS_NO_SANDBOX
-    LogMsg("Note: Compiled without Apple Sandbox support");
-#else // MDNS_NO_SANDBOX
-    if (!sandbox_init)
-        LogMsg("Note: Running without Apple Sandbox support (not available on this OS)");
-    else
+    char *sandbox_msg;
+    uint64_t sandbox_flags = SANDBOX_NAMED;
+
+    (void)confstr(_CS_DARWIN_USER_CACHE_DIR, NULL, 0);
+
+    int sandbox_err = sandbox_init("mDNSResponder", sandbox_flags, &sandbox_msg);
+    if (sandbox_err)
     {
-        char *sandbox_msg;
-        uint64_t sandbox_flags = SANDBOX_NAMED;
-
-        (void)confstr(_CS_DARWIN_USER_CACHE_DIR, NULL, 0);
-
-        int sandbox_err = sandbox_init("mDNSResponder", sandbox_flags, &sandbox_msg);
-        if (sandbox_err)
-        {
-            LogMsg("WARNING: sandbox_init error %s", sandbox_msg);
-            // If we have errors in the sandbox during development, to prevent
-            // exiting, uncomment the following line.
-            //sandbox_free_error(sandbox_msg);
-            
-            errx(EX_OSERR, "sandbox_init() failed: %s", sandbox_msg);
-        }
-        else LogInfo("Now running under Apple Sandbox restrictions");
+        LogMsg("WARNING: sandbox_init error %s", sandbox_msg);
+        // If we have errors in the sandbox during development, to prevent
+        // exiting, uncomment the following line.
+        //sandbox_free_error(sandbox_msg);
+        
+        errx(EX_OSERR, "sandbox_init() failed: %s", sandbox_msg);
     }
-#endif // MDNS_NO_SANDBOX
+    else LogInfo("Now running under Apple Sandbox restrictions");
+#else
+    // For non-macOS OSes, mDNSResponder relies on the com.apple.private.sandbox.profile:embedded entry in its
+    // entitlements plist to cause mDNSResponder to be sandboxed as soon as it starts.
+    const int result = sandbox_check(getpid(), NULL, SANDBOX_FILTER_NONE);
+    switch (result)
+    {
+        case 1:
+            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
+                "mDNSResponder is sandboxed via com.apple.private.sandbox.profile:embedded entitlement");
+            break;
+
+        case 0:
+            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_FAULT,
+                "mDNSResponder is not sandboxed (check for com.apple.private.sandbox.profile:embedded entitlement)");
+            break;
+
+        case -1:
+        default:
+        {
+            const long error = (result == -1) ? errno : result;
+            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_FAULT,
+                "Couldn't determine if mDNSResponder is sandboxed because of sandbox_check() error: " PUB_OS_ERR, error);
+            break;
+        }
+    }
+#endif
 }
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
@@ -1476,6 +1542,7 @@
     os_log_t mDNSLogCategory_ ## NAME ## _redacted  = NULL
 
 MDNS_OS_LOG_CATEGORY_DECLARE(Default);
+MDNS_OS_LOG_CATEGORY_DECLARE(State);
 MDNS_OS_LOG_CATEGORY_DECLARE(mDNS);
 MDNS_OS_LOG_CATEGORY_DECLARE(uDNS);
 MDNS_OS_LOG_CATEGORY_DECLARE(SPS);
@@ -1488,6 +1555,7 @@
 mDNSlocal void init_logging(void)
 {
     MDNS_OS_LOG_CATEGORY_INIT(Default);
+    MDNS_OS_LOG_CATEGORY_INIT(State);
     MDNS_OS_LOG_CATEGORY_INIT(mDNS);
     MDNS_OS_LOG_CATEGORY_INIT(uDNS);
     MDNS_OS_LOG_CATEGORY_INIT(SPS);
@@ -1514,7 +1582,6 @@
 
 #if DEBUG
     bool useDebugSocket = mDNSfalse;
-    bool useSandbox = mDNStrue;
 #endif
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, OS_LOG)
@@ -1560,7 +1627,6 @@
         if (!strcasecmp(argv[i], "-DisableAllowExpired"      )) EnableAllowExpired        = mDNSfalse;
 #if DEBUG
         if (!strcasecmp(argv[i], "-UseDebugSocket"))            useDebugSocket = mDNStrue;
-        if (!strcasecmp(argv[i], "-NoSandbox"))                 useSandbox = mDNSfalse;
 #endif    
     }
 
@@ -1587,15 +1653,18 @@
 
 #ifndef MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM
 
-    signal(SIGHUP,  HandleSIG);     // (Debugging) Purge the cache to check for cache handling bugs
-    signal(SIGINT,  HandleSIG);     // Ctrl-C: Detach from Mach BootstrapService and exit cleanly
-    signal(SIGPIPE,   SIG_IGN);     // Don't want SIGPIPE signals -- we'll handle EPIPE errors directly
-    signal(SIGTERM, HandleSIG);     // Machine shutting down: Detach from and exit cleanly like Ctrl-C
-    signal(SIGINFO, HandleSIG);     // (Debugging) Write state snapshot to syslog
-    signal(SIGUSR1, HandleSIG);     // (Debugging) Enable Logging
-    signal(SIGUSR2, HandleSIG);     // (Debugging) Enable Packet Logging
-    signal(SIGPROF, HandleSIG);     // (Debugging) Toggle Multicast Logging
-    signal(SIGTSTP, HandleSIG);     // (Debugging) Disable all Debug Logging (USR1/USR2/PROF)
+    signal(SIGHUP,   HandleSIG);     // (Debugging) Purge the cache to check for cache handling bugs
+    signal(SIGINT,   HandleSIG);     // Ctrl-C: Detach from Mach BootstrapService and exit cleanly
+    signal(SIGPIPE,    SIG_IGN);     // Don't want SIGPIPE signals -- we'll handle EPIPE errors directly
+    signal(SIGTERM,  HandleSIG);     // Machine shutting down: Detach from and exit cleanly like Ctrl-C
+    signal(SIGINFO,  HandleSIG);     // (Debugging) Write state snapshot to syslog
+    signal(SIGUSR1,  HandleSIG);     // (Debugging) Enable Logging
+    signal(SIGUSR2,  HandleSIG);     // (Debugging) Enable Packet Logging
+    signal(SIGPROF,  HandleSIG);     // (Debugging) Toggle Multicast Logging
+    signal(SIGTSTP,  HandleSIG);     // (Debugging) Disable all Debug Logging (USR1/USR2/PROF)
+#if MDNSRESPONDER_SUPPORTS(APPLE, UNICAST_ASSIST)
+    signal(SIGWINCH, HandleSIG);     // (Debugging) Purge the unicast assist cache for performance testing
+#endif
 
 #endif // MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM
 
@@ -1639,9 +1708,6 @@
 
 #endif // MDNSRESPONDER_USES_LIB_DISPATCH_AS_PRIMARY_EVENT_LOOP_MECHANISM
 
-#if DEBUG
-    if (useSandbox)
-#endif
     SandboxProcess();
 
     status = mDNSDaemonInitialize();
diff --git a/mDNSMacOSX/discovery_proxy.c b/mDNSMacOSX/discovery_proxy.c
new file mode 100644
index 0000000..182ae30
--- /dev/null
+++ b/mDNSMacOSX/discovery_proxy.c
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2023-2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "discovery_proxy.h"
+
+#include "mDNSFeatures.h"
+#if MDNSRESPONDER_SUPPORTS(APPLE, TERMINUS_ASSISTED_UNICAST_DISCOVERY)
+
+#include "general.h"
+#include "mDNSMacOSX.h"
+#include "QuerierSupport.h"
+#include "tls-keychain.h"
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <CoreUtils/CommonServices.h>
+#include <MacTypes.h>
+#include <stdint.h>
+
+#include "mdns_strict.h"
+
+//======================================================================================================================
+
+static mdns_dns_service_id_t g_discovery_proxy_service_id = MDNS_DNS_SERVICE_INVALID_ID;
+
+//======================================================================================================================
+// MARK: - Internal Functions
+
+static void
+_discovery_proxy_stop_internal(void)
+{
+	if (g_discovery_proxy_service_id != MDNS_DNS_SERVICE_INVALID_ID) {
+		Querier_DeregisterCustomPushDNSService(g_discovery_proxy_service_id);
+		LogRedact(MDNS_LOG_CATEGORY_UDNS, MDNS_LOG_DEFAULT, "Discovery proxy service deregistered -- id: %" PRIu64,
+			g_discovery_proxy_service_id);
+		g_discovery_proxy_service_id = MDNS_DNS_SERVICE_INVALID_ID;
+	}
+}
+
+static OSStatus
+_discovery_proxy_stop_handler(void)
+{
+	OSStatus err = kNoErr;
+	KQueueLock();
+	_discovery_proxy_stop_internal();
+	KQueueUnlock("discovery_proxy_stop_handler");
+	return err;
+}
+
+//======================================================================================================================
+
+static mdns_dns_push_service_definition_t
+_discovery_proxy_create_push_service_definition(const uint32_t ifindex, const CFArrayRef addresses,
+	const CFArrayRef match_domains, const CFArrayRef trusted_certs, OSStatus * const out_error)
+{
+	OSStatus err;
+	mdns_dns_push_service_definition_t result = NULL;
+	mdns_dns_push_service_definition_t service_definition;
+
+	service_definition = mdns_dns_push_service_definition_create();
+	mdns_require_action_quiet(service_definition, exit, err = kNoResourcesErr);
+
+	// The interface index must be non-zero for the discovery proxy.
+	mdns_require_action_quiet(ifindex != 0, exit, err = kParamErr);
+	mdns_dns_push_service_definition_set_interface_index(service_definition, ifindex,
+		mdns_dns_service_interface_scope_unscoped_and_scoped);
+
+	mdns_dns_push_service_definition_set_local_purview(service_definition, true);
+	mdns_dns_push_service_definition_set_mdns_alternative(service_definition, true);
+
+	const CFIndex n_address = CFArrayGetCount(addresses);
+	for (CFIndex i = 0; i < n_address; i++) {
+		const mdns_address_t server_address = (mdns_address_t)CFArrayGetValueAtIndex(addresses, i);
+		mdns_dns_push_service_definition_append_server_address(service_definition, server_address);
+	}
+	const CFIndex n_match_domains = CFArrayGetCount(match_domains);
+	for (CFIndex i = 0; i < n_match_domains; i++) {
+		const mdns_domain_name_t match_domain = (mdns_domain_name_t)CFArrayGetValueAtIndex(match_domains, i);
+		mdns_dns_push_service_definition_add_domain(service_definition, match_domain);
+	}
+	const CFIndex n_trusted_certs = CFArrayGetCount(trusted_certs);
+	for (CFIndex i = 0; i < n_trusted_certs; i++) {
+		const CFDataRef trusted_cert = (CFDataRef)CFArrayGetValueAtIndex(trusted_certs, i);
+		mdns_dns_push_service_definition_append_trusted_certificate(service_definition, trusted_cert);
+	}
+	result = service_definition;
+	service_definition = NULL;
+	err = kNoErr;
+
+exit:
+	mdns_forget(&service_definition);
+	if (out_error) {
+		*out_error = err;
+	}
+	return result;
+}
+
+//======================================================================================================================
+
+static OSStatus
+_discovery_proxy_start_handler(const uint32_t ifindex, const CFArrayRef addresses, const CFArrayRef match_domains,
+	const CFArrayRef server_certificates)
+{
+	OSStatus err = 0;
+	mdns_dns_push_service_definition_t service_definition = NULL;
+	KQueueLock();
+
+	_discovery_proxy_stop_internal();
+
+	service_definition = _discovery_proxy_create_push_service_definition(ifindex, addresses, match_domains,
+		server_certificates, &err);
+	mdns_require_noerr_quiet(err, exit);
+
+	g_discovery_proxy_service_id = Querier_RegisterCustomPushDNSService(service_definition);
+	mdns_require_action_quiet(g_discovery_proxy_service_id != MDNS_DNS_SERVICE_INVALID_ID, exit, err = kNotHandledErr);
+
+	LogRedact(MDNS_LOG_CATEGORY_UDNS, MDNS_LOG_DEFAULT, "Discovery proxy service registered -- id: %" PRIu64,
+		g_discovery_proxy_service_id);
+
+exit:
+	mdns_forget(&service_definition);
+	KQueueUnlock("discovery_proxy_start_handler");
+	return err;
+}
+
+#endif // MDNSRESPONDER_SUPPORTS(APPLE, TERMINUS_ASSISTED_UNICAST_DISCOVERY)
+
+//======================================================================================================================
+// MARK: - External Variables
+
+const struct mrcs_server_discovery_proxy_handlers_s kMRCSServerDiscoveryProxyHandlers =
+{
+#if MDNSRESPONDER_SUPPORTS(APPLE, TERMINUS_ASSISTED_UNICAST_DISCOVERY)
+	.start = _discovery_proxy_start_handler,
+	.stop = _discovery_proxy_stop_handler,
+#else
+	.start = NULL,
+	.stop = NULL,
+#endif
+};
diff --git a/mDNSMacOSX/discovery_proxy.h b/mDNSMacOSX/discovery_proxy.h
new file mode 100644
index 0000000..93dbda1
--- /dev/null
+++ b/mDNSMacOSX/discovery_proxy.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2023 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef __DISCOVERY_PROXY_H__
+#define __DISCOVERY_PROXY_H__
+
+#include "mrcs_server.h"
+
+extern const struct mrcs_server_discovery_proxy_handlers_s kMRCSServerDiscoveryProxyHandlers;
+
+#endif // __DISCOVERY_PROXY_H__
diff --git a/mDNSMacOSX/dns_push/dns_push_discovery.c b/mDNSMacOSX/dns_push/dns_push_discovery.c
index d64d1cf..6fc40aa 100644
--- a/mDNSMacOSX/dns_push/dns_push_discovery.c
+++ b/mDNSMacOSX/dns_push/dns_push_discovery.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2022-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -23,14 +23,16 @@
 #include "dns_push_discovery.h"
 
 #include "discover_resolver.h"
-#include "dns_push_obj_discovered_service_manager.h"
 #include "dns_push_obj_dns_question_member.h"
+#include "dns_obj_log.h"
 #include "dns_obj_rr.h"
 #include "dns_obj_rr_srv.h"
 #include "dns_push_obj_context.h"
 #include "dns_push_mdns_core.h"
 #include "dns_common.h"
 #include "DNSCommon.h"
+#include "mDNSMacOSX.h"
+#include "QuerierSupport.h"
 #include "uDNS.h"
 
 #include "dns_assert_macros.h"
@@ -39,40 +41,64 @@
 //======================================================================================================================
 // MARK: - Local Structures
 
-// g_discovered_service_managers is responsible for registering/deregistering DNS push service for non-local domain. It
-// has a "use_count" that remembers how many questions are using the registered DNS push service, when the "use_count"
-// becomes zero or the authoritative zone goes away, the corresponding service will be deregistered (no matter what value
-// "use_count" has).
-
-typedef struct discovered_service_manager_list_item_t discovered_service_manager_list_item_t;
-
-struct discovered_service_manager_list_item_t {
-	dns_push_obj_discovered_service_manager_t	manager;
-	discovered_service_manager_list_item_t		*next;
-	size_t										use_count;
+typedef struct interface_monitor_list_s interface_monitor_list_s;
+struct interface_monitor_list_s {
+	mdns_interface_monitor_t	monitor;	// The interface monitor object.
+	size_t						use_count;	// The number active push questions that use this interface monitor.
+	interface_monitor_list_s	*next;		// The next monitor in the list.
 };
+static interface_monitor_list_s *g_interface_monitors; // The global interface monitor list.
 
-static discovered_service_manager_list_item_t *g_discovered_service_managers;
+extern mDNS mDNSStorage;
 
 //======================================================================================================================
 // MARK: - Local Prototypes
 
+static dispatch_queue_t
+_dns_push_discovery_interface_monitor_queue(void);
+
 static DNSQuestion *
-_dns_push_discover_start_discovery(mDNS *m, const DNSQuestion *question, dns_push_obj_context_t context);
+_dns_push_discovery_start_soa(mDNS *m, const DNSQuestion *question, dns_push_obj_context_t context);
+
+static DNSQuestion *
+_dns_push_discovery_start_srv(mDNS *m, mDNSInterfaceID if_id, dns_push_obj_context_t context);
+
+static void
+_dns_push_discovery_stop(mDNS *m, DNSQuestion **q_pptr, dns_push_obj_context_t context, bool hold_lock);
 
 static void
 _dns_push_discovery_soa_result_reply(mDNS *m, DNSQuestion *question, const ResourceRecord *answer, QC_result event);
 
 static void
-_dns_push_discovery_try_next_qname(mDNS *m, DNSQuestion *question, dns_obj_domain_name_t new_q_name);
-
-static dns_obj_error_t
-_dns_push_discovery_register_push_service(dns_push_obj_context_t context, dns_obj_domain_name_t name,
-	uint32_t if_index);
+_dns_push_discovery_srv_result_reply(mDNS *m, DNSQuestion *question, const ResourceRecord *answer, QC_result event);
 
 static void
-_dns_push_discovery_deregister_push_service(dns_push_obj_discovered_service_manager_t manager,
-	bool deregister_unconditionally);
+_dns_push_discovery_fallback_to_dns_polling(dns_push_obj_context_t context);
+
+static void
+_dns_push_discovery_stop_dns_polling(dns_push_obj_context_t context);
+
+static void
+_dns_push_discovery_try_next_qname(mDNS *m, DNSQuestion *question, dns_obj_domain_name_t new_q_name);
+
+static mdns_interface_monitor_t
+_dns_push_discovery_start_interface_monitor(uint32_t if_index);
+
+static mdns_interface_monitor_t
+_dns_push_discovery_start_mdns_interface_monitor(uint32_t if_index);
+
+static void
+_dns_push_discovery_process_interface_changes(mDNS *m, mdns_interface_monitor_t monitor);
+
+static void
+_dns_push_discovery_stop_interface_monitor(mdns_interface_monitor_t monitor);
+
+static mdns_dns_service_id_t
+_dns_push_discovery_register_push_service(dns_push_obj_context_t context, dns_obj_domain_name_t name,
+	uint32_t if_index, bool *out_duplicate, dns_obj_error_t *out_error);
+
+static void
+_dns_push_discovery_deregister_push_service(dns_push_obj_context_t context);
 
 //======================================================================================================================
 // MARK: - Public Functions
@@ -90,16 +116,17 @@
 	mdns_require_action_quiet(dns_question_member != NULL, exit, err = DNS_OBJ_ERROR_NO_MEMORY);
 
 	dns_push_obj_replace(&question->dns_push, dns_question_member);
+	question->LongLived = false; // Disable old DNS push code.
 
-	context = dns_push_obj_context_create(m, question, &err);
+	context = dns_push_obj_context_create(question, &err);
 	mdns_require_noerr_quiet(err, exit);
 
 	dns_push_obj_dns_question_member_set_context(dns_question_member, context);
 
-	DNSQuestion * const soa_q = _dns_push_discover_start_discovery(m, question, context);
+	DNSQuestion * const soa_q = _dns_push_discovery_start_soa(m, question, context);
 	mdns_require_action_quiet(soa_q, exit, err = DNS_OBJ_ERROR_NO_RESOURCES);
 
-	dns_push_obj_context_set_soa_question(context, soa_q);
+	dns_push_obj_context_set_discovery_question(context, soa_q);
 	err = DNS_OBJ_ERROR_NO_ERROR;
 
 exit:
@@ -116,34 +143,44 @@
 void
 dns_push_handle_question_stop(mDNS * const m, DNSQuestion * const question)
 {
-	mdns_require_return(question->dns_push);
-
-	const dns_push_obj_context_t context = dns_push_obj_dns_question_member_get_context(question->dns_push);
+	const dns_push_obj_context_t context = dns_question_get_dns_push_context(question);
 	mdns_require_return(context);
 
-	// Stop the SOA question that is created to determine the authoritative zone.
-	DNSQuestion *soa_question = dns_push_obj_context_get_soa_question(context);
-	if (soa_question) {
-		mDNS_StopQuery_internal(m, soa_question);
-		mdns_free(soa_question);
-		dns_push_obj_context_set_soa_question(context, NULL);
+	// Stop the SOA or SRV question that is started to determine the authoritative zone and the push capability.
+	DNSQuestion *discovery_question = dns_push_obj_context_get_discovery_question(context);
+	if (discovery_question) {
+		_dns_push_discovery_stop(m, &discovery_question, context, false);
+		dns_push_obj_context_set_discovery_question(context, NULL);
 	}
+	_dns_push_discovery_deregister_push_service(context);
 
-	// Decrease the use count and release the service manager reference hold by the context.
-	const dns_push_obj_discovered_service_manager_t service_manager = dns_push_obj_context_get_service_manager(context);
-	if (service_manager) {
-		_dns_push_discovery_deregister_push_service(service_manager, false);
-		dns_push_obj_context_set_service_manager(context, NULL);
+	const mdns_interface_monitor_t monitor = dns_push_obj_context_get_interface_monitor(context);
+	if (monitor) {
+		_dns_push_discovery_stop_interface_monitor(monitor);
+		dns_push_obj_context_set_interface_monitor(context, NULL);
 	}
 
 	dns_push_obj_forget(&question->dns_push);
+	question->LongLived = mDNStrue;
 }
 
 //======================================================================================================================
 // MARK: - Private Functions
 
+static dispatch_queue_t
+_dns_push_discovery_interface_monitor_queue(void)
+{
+	static dispatch_once_t s_once = 0;
+	static dispatch_queue_t s_queue = NULL;
+	dispatch_once(&s_once,
+	^{
+		s_queue = dispatch_queue_create("com.apple.dns-push.interface-monitor", DISPATCH_QUEUE_SERIAL);
+	});
+	return s_queue;
+}
+
 static DNSQuestion *
-_dns_push_discover_start_discovery(mDNS * const m, const DNSQuestion * const q, const dns_push_obj_context_t context)
+_dns_push_discovery_start_soa(mDNS * const m, const DNSQuestion * const q, const dns_push_obj_context_t context)
 {
 	DNSQuestion *soa_q = NULL;
 	bool failed = false;
@@ -161,6 +198,10 @@
 	soa_q->QuestionContext = context;
 	soa_q->ReturnIntermed = mDNStrue;
 
+	// Must not set soa_q->LongLived to true because we are in the process of preparing DNS push. Setting it to true
+	// will cause infinite recursive call.
+	soa_q->LongLived = mDNSfalse;
+
 	const mStatus q_start_error = mDNS_StartQuery_internal(m, soa_q);
 	mdns_require_noerr_action_quiet(q_start_error, exit, failed = true);
 
@@ -174,73 +215,159 @@
 //======================================================================================================================
 
 static void
-_dns_push_discovery_soa_result_reply(mDNS * const m, DNSQuestion * const question, const ResourceRecord * const answer,
+_dns_push_discovery_soa_result_reply(mDNS * const m, DNSQuestion *question, const ResourceRecord * const answer,
 	const QC_result event)
 {
 	dns_obj_error_t err;
 	dns_obj_domain_name_t zone_cut = NULL;
-	dns_obj_domain_name_t current_qname = NULL;
 	dns_obj_domain_name_t next_qname = NULL;
+	bool stop_discovery = true;
 
+	const dns_push_obj_context_t context = ((dns_push_obj_context_t)question->QuestionContext);
+
+	// We only handle the add event, for other events we ignore them, for example:
 	// When the query is suppressed due to no DNS service, we return directly and wait for the DNS service being
 	// configured by resolver discovery.
-	if (event == QC_suppressed) {
-		return;
+	if (event != QC_add) {
+		stop_discovery = false;
+		goto exit;
 	}
 
-	const dns_push_obj_context_t context = (dns_push_obj_context_t) question->QuestionContext;
-	const dns_push_obj_discovered_service_manager_t manager = dns_push_obj_context_get_service_manager(context);
-
-	if (event == QC_add) {
-		if (answer->RecordType == kDNSRecordTypePacketNegative) {
-			// No Data or NXDomain.
-			// The current name is not an authoritative zone, striping one label off and try the new name (Finished
-			// below).
-		} else {
-			// Positive SOA.
-			require(SameDomainName(&question->qname, answer->name), exit);
-			require(answer->rrtype == kDNSRecordType_SOA, exit);
-
-			zone_cut = dns_obj_domain_name_create_with_labels(answer->name->c, false, &err);
-			require_noerr(err, exit);
-		}
-
-		if (zone_cut != NULL) {
-			mdns_require_quiet(!manager, exit);
-
-			// Have found the zone cut.
-			dns_push_obj_context_set_authoritative_zone(context, zone_cut);
-
-			const uint32_t if_index = (uint32_t)((uintptr_t)answer->InterfaceID);
-			err = _dns_push_discovery_register_push_service(context, zone_cut, if_index);
-			mdns_require_noerr_quiet(err, exit);
-
-		} else {
-			// Have not found the zone cut, try its parent label until root label.
-			if (!next_qname) {
-				mdns_require_quiet(!IsRootDomain(&question->qname), exit);
-				const uint8_t * const next_qname_labels = (SecondLabel(&question->qname)->c);
-				next_qname = dns_obj_domain_name_create_with_labels(next_qname_labels, true, &err);
-				mdns_require_noerr_quiet(err, exit);
-			}
-			_dns_push_discovery_try_next_qname(m, question, next_qname);
-		}
+	if (answer->RecordType == kDNSRecordTypePacketNegative) {
+		// No Data or NXDomain.
+		// The current name is not an authoritative zone, striping one label off and try the new name (Finished
+		// below).
 	} else {
-		// If we received a remove event, then the authoritative zone has gone, so the corresponding DNS push service
-		// should also be deregistered.
-		mdns_require_quiet(event == QC_rmv, exit);
-		mdns_require_quiet(manager, exit);
+		// Positive SOA.
+		mdns_require_quiet(answer->rrtype == kDNSRecordType_SOA, exit);
+		zone_cut = dns_obj_domain_name_create_with_labels(answer->name->c, true, &err);
+		mdns_require_noerr_quiet(err, exit);
+	}
 
-		dns_push_obj_context_set_authoritative_zone(context, NULL);
+	if (zone_cut != NULL) {
+		// Have found the zone cut.
+		dns_push_obj_context_set_authoritative_zone(context, zone_cut);
 
-		_dns_push_discovery_deregister_push_service(manager, true);
-		dns_push_obj_context_set_service_manager(context, NULL);
+		// Stop SOA query.
+		_dns_push_discovery_stop(m, &question, context, true);
+		// Do not touch `question` because question has been freed by `_dns_push_discovery_stop` above.
+		stop_discovery = false;
+
+		mDNS_Lock(m);
+		const uint32_t if_index = mDNSPlatformInterfaceIndexfromInterfaceID(m, answer->InterfaceID, true);
+		mDNS_Unlock(m);
+		dns_push_obj_context_set_interface_index(context, if_index);
+
+		// Query for the push capability of the server.
+		DNSQuestion * const srv_question = _dns_push_discovery_start_srv(m, answer->InterfaceID, context);
+		mdns_require_action_quiet(srv_question, exit, err = DNS_OBJ_ERROR_NO_RESOURCES);
+
+		// We have found the zone cut, continue the SRV probing for discovery.
+		dns_push_obj_context_set_discovery_question(context, srv_question);
+	} else {
+		// Have not found the zone cut, try its parent label until root label.
+		mdns_require_quiet(!IsRootDomain(&question->qname), exit);
+
+		const uint8_t * const next_qname_labels = (SecondLabel(&question->qname)->c);
+		next_qname = dns_obj_domain_name_create_with_labels(next_qname_labels, true, &err);
+		mdns_require_noerr_quiet(err, exit);
+
+		_dns_push_discovery_try_next_qname(m, question, next_qname);
+		stop_discovery = false;
 	}
 
 exit:
 	dns_obj_forget(&zone_cut);
-	dns_obj_forget(&current_qname);
 	dns_obj_forget(&next_qname);
+	if (stop_discovery) {
+		_dns_push_discovery_stop(m, &question, context, true);
+	}
+}
+
+//======================================================================================================================
+
+static void
+_dns_push_discovery_srv_result_reply(mDNS * const m, DNSQuestion *question, const ResourceRecord * const answer,
+	const QC_result event)
+{
+	dns_obj_error_t err;
+	const dns_push_obj_context_t context = ((dns_push_obj_context_t)question->QuestionContext);
+	DNSQuestion * const original_q = dns_push_obj_context_get_original_question(context);
+	const mDNSu16 qid = mDNSVal16(original_q->TargetQID);
+
+	mdns_require_quiet(event == QC_add, exit);
+	if (answer->RecordType == kDNSRecordTypePacketNegative) {
+		// No Data or NXDomain, the server does not supports DNS push for now, but keep monitoring changes in case the
+		// network supports it.
+		log_default("[Q%u] Current network does not support DNS push, falling back to DNS polling -- service ID: %llu",
+			qid, mdns_dns_service_get_id(question->dnsservice));
+		_dns_push_discovery_fallback_to_dns_polling(context);
+		goto exit;
+	}
+
+	const dns_obj_domain_name_t authoritative_zone = dns_push_obj_context_get_authoritative_zone(context);
+	mdns_require_quiet(authoritative_zone, exit);
+
+	mDNS_Lock(m);
+	const uint32_t if_index = mDNSPlatformInterfaceIndexfromInterfaceID(m, answer->InterfaceID, true);
+	mDNS_Unlock(m);
+
+	bool re_registered;
+	const mdns_dns_service_id_t ident = _dns_push_discovery_register_push_service(context, authoritative_zone,
+		if_index, &re_registered, &err);
+	if ((ident == MDNS_DNS_SERVICE_INVALID_ID) && err) {
+		log_fault("[Q%u] Failed to register push service -- id: %llu"
+			"authoritative zone: " PUB_DNS_DM_NAME ", interface index: %u, error: " PUB_OS_ERR,
+			qid, ident, DNS_DM_NAME_PARAM(authoritative_zone), if_index, (long)err);
+		goto exit;
+	}
+	if (re_registered) {
+		log_default("[Q%u] DNS push discovery finished -- service id: %llu, re registered: " PUB_BOOL,
+			qid, ident, BOOL_PARAM(re_registered));
+	} else {
+		log_default("[Q%u] DNS push discovery finished, using service with SRV name _dns-push-tls._tcp." PUB_DNS_DM_NAME
+			" -- service id: %llu, re registered: " PUB_BOOL, qid, DNS_DM_NAME_PARAM(authoritative_zone), ident,
+			BOOL_PARAM(re_registered));
+	}
+	// The current network supports DNS push, so stop the discovery.
+	_dns_push_discovery_stop(m, &question, context, true);
+	// If the question has been using DNS polling since last update, stop DNS polling because a push service is now
+	// available.
+	_dns_push_discovery_stop_dns_polling(context);
+
+exit:
+	return;
+}
+
+//======================================================================================================================
+
+static void
+_dns_push_discovery_fallback_to_dns_polling(const dns_push_obj_context_t context)
+{
+	const DNSQuestion *const q = dns_push_obj_context_get_original_question(context);
+	const mDNSu32 request_id = q->request_id;
+	const mDNSu16 question_id = mDNSVal16(q->TargetQID);
+
+	log_default("[R%u->Q%u] Starting long-lived DNS polling -- "
+		"polling interval: " STRINGIFY(LLQ_POLL_INTERVAL_MIN) " min", request_id, question_id);
+
+	dns_push_obj_context_set_dns_polling_enabled(context, true);
+	Querier_ProcessDNSServiceChangesAsync(mDNStrue);
+}
+
+//======================================================================================================================
+
+static void
+_dns_push_discovery_stop_dns_polling(const dns_push_obj_context_t context)
+{
+	const DNSQuestion *const q = dns_push_obj_context_get_original_question(context);
+	const mDNSu32 request_id = q->request_id;
+	const mDNSu16 question_id = mDNSVal16(q->TargetQID);
+
+	log_default("[R%u->Q%u] Stopping long-lived DNS polling", request_id, question_id);
+
+	dns_push_obj_context_set_dns_polling_enabled(context, false);
+	Querier_ProcessDNSServiceChangesAsync(mDNStrue);
 }
 
 //======================================================================================================================
@@ -250,6 +377,8 @@
 {
 	m->RestartQuestion = question;
 	mDNS_StopQuery(m, question);
+	// Must set RestartQuestion back to NULL once the restarting question has been stopped.
+	m->RestartQuestion = mDNSNULL;
 
 	const uint8_t * const labels = dns_obj_domain_name_get_labels(new_q_name);
 	const size_t labels_length = dns_obj_domain_name_get_length(new_q_name);
@@ -263,70 +392,303 @@
 
 //======================================================================================================================
 
-static dns_obj_error_t
-_dns_push_discovery_register_push_service(const dns_push_obj_context_t context,
-	const dns_obj_domain_name_t zone, const uint32_t if_index)
+static mdns_interface_monitor_t
+_dns_push_discovery_start_interface_monitor(const uint32_t if_index)
 {
-	dns_obj_error_t err;
+	interface_monitor_list_s *monitor_in_list = NULL;
+	mdns_interface_monitor_t monitor = NULL;
 
-	// Check if we already have a registered manager that manages the required DNS push service.
-	dns_push_obj_discovered_service_manager_t manager = NULL;
-	discovered_service_manager_list_item_t **pptr = &g_discovered_service_managers;
-	while (*pptr) {
-		if (dns_push_obj_discovered_service_manager_manages_this_zone((*pptr)->manager, zone, if_index)) {
+	// Search for any existing interface monitor for the same interface.
+	monitor_in_list = g_interface_monitors;
+	while (monitor_in_list) {
+		if (mdns_interface_monitor_get_interface_index(monitor_in_list->monitor) == if_index) {
 			break;
 		}
+		monitor_in_list = monitor_in_list->next;
 	}
-	if ((*pptr)) {
-		// If we have found an existing one, use it.
-		manager = (*pptr)->manager;
-		dns_push_obj_retain(manager);
+	if (monitor_in_list == NULL) {
+		// If not found, create a new interface monitor and append it.
+		monitor_in_list = mdns_calloc(1, sizeof(*monitor_in_list));
+		mdns_require_quiet(monitor_in_list, exit);
+
+		// Activate the monitor.
+		monitor = _dns_push_discovery_start_mdns_interface_monitor(if_index);
+		mdns_require_quiet(monitor, exit);
+
+		monitor_in_list->monitor = monitor;
+		mdns_retain(monitor_in_list->monitor);
+
+		// Add the monitor to the list.
+		interface_monitor_list_s **pptr = &g_interface_monitors;
+		while (*pptr) {
+			pptr = &((*pptr)->next);
+		}
+		*pptr = monitor_in_list;
 	} else {
-		// Otherwise, create a new one.
-		manager = dns_push_obj_discovered_service_manager_create(zone, if_index, &err);
-		mdns_require_noerr_quiet(err, exit);
-
-		*pptr = mdns_calloc(1, sizeof(*(*pptr)));
-		mdns_require_action_quiet(*pptr, exit, err = DNS_OBJ_ERROR_NO_MEMORY);
-
-		(*pptr)->manager = manager;
-		dns_push_obj_retain((*pptr)->manager);
+		// If found, retain it directly.
+		monitor = monitor_in_list->monitor;
+		mdns_retain(monitor);
 	}
-	// Increase the use count.
-	(*pptr)->use_count++;
-	dns_push_obj_context_set_service_manager(context, manager);
-	err = DNS_OBJ_ERROR_NO_ERROR;
+	monitor_in_list->use_count++;
+	monitor_in_list = NULL;
 
 exit:
-	dns_push_obj_forget(&manager);
-	return err;
+	mdns_free(monitor_in_list);
+	return monitor;
+}
+
+//======================================================================================================================
+
+static mdns_interface_monitor_t
+_dns_push_discovery_start_mdns_interface_monitor(const uint32_t if_index)
+{
+	mdns_interface_monitor_t monitor = NULL;
+	mdns_interface_monitor_t obj = mdns_interface_monitor_create(if_index);
+	mdns_require_quiet(obj, exit);
+
+	mDNS * const m = &mDNSStorage;
+	mdns_interface_monitor_set_queue(obj, _dns_push_discovery_interface_monitor_queue());
+
+	mdns_retain(obj);
+	mdns_interface_monitor_set_event_handler(obj,
+	^(mdns_event_t event, __unused OSStatus error) {
+		if (event == mdns_event_invalidated) {
+			mdns_release(obj);
+		} else if (event == mdns_event_error) {
+			_dns_push_discovery_process_interface_changes(m, obj);
+		}
+	});
+	mdns_interface_monitor_set_update_handler(obj,
+	^(const mdns_interface_flags_t update_flags) {
+		const bool network_changed = ((update_flags & mdns_interface_flag_network) != 0);
+		// Only restart the push discovery when the network has changed due to interface changes.
+		if (network_changed) {
+			_dns_push_discovery_process_interface_changes(m, obj);
+		}
+	});
+	mdns_interface_monitor_activate(obj);
+
+	monitor = obj;
+	obj = NULL;
+
+exit:
+	mdns_forget(&obj);
+	return monitor;
 }
 
 //======================================================================================================================
 
 static void
-_dns_push_discovery_deregister_push_service(const dns_push_obj_discovered_service_manager_t manager,
-	const bool deregister_unconditionally)
+_dns_push_discovery_process_interface_changes(mDNS * const m, const mdns_interface_monitor_t monitor)
 {
-	discovered_service_manager_list_item_t **pptr = &g_discovered_service_managers;
+	const uint32_t if_index = mdns_interface_monitor_get_interface_index(monitor);
+
+	KQueueLock();
+	mDNS_Lock(m);
+
+	const mdns_dns_service_manager_t service_manager = Querier_GetDNSServiceManager();
+
+	size_t q_count = 0;
+	for (const DNSQuestion *q = m->Questions; q; q = q->next) {
+		q_count++;
+	}
+	size_t restarted_push_q_count = 0;
+	// We are not trying to restart the push question itself here but we are using RestartQuestion pointer to indicate
+	// that we are restarting push discovery.
+	m->RestartQuestion = m->Questions;
+	for (size_t i = 0; (i < q_count) && m->RestartQuestion; i++) {
+		DNSQuestion *const q = m->RestartQuestion;
+
+		const dns_push_obj_dns_question_member_t dns_push = q->dns_push;
+		const dns_push_obj_context_t context =
+			dns_push ? dns_push_obj_dns_question_member_get_context(dns_push) : NULL;
+		const mdns_interface_monitor_t if_monitor =
+			context ? dns_push_obj_context_get_interface_monitor(context) : NULL;
+		if (if_monitor && (if_monitor == monitor)) {
+			// The push question is managed by this interface monitor.
+			restarted_push_q_count++;
+		} else {
+			m->RestartQuestion = q->next;
+			continue;
+		}
+		const mdns_dns_service_id_t service_id = dns_push_obj_context_get_service_id(context);
+		mdns_dns_service_manager_terminate_discovered_push_service(service_manager, service_id);
+
+		// Restart push discovery.
+		dns_push_handle_question_stop(m, q);
+		dns_push_handle_question_start(m, q);
+
+		if (m->RestartQuestion == q) {
+			m->RestartQuestion = q->next;
+		}
+	}
+	m->RestartQuestion = mDNSNULL;
+	log_default("Network changes, restarting all push questions that are related to the changed interface -- "
+		"if_index: %u, restarted count: %zu", if_index, restarted_push_q_count);
+
+	mDNS_Unlock(m);
+	KQueueUnlock("DNS push interface monitor");
+}
+
+//======================================================================================================================
+
+static void
+_dns_push_discovery_stop_interface_monitor(const mdns_interface_monitor_t monitor)
+{
+	interface_monitor_list_s **pptr = &g_interface_monitors;
 	while (*pptr) {
-		if ((*pptr)->manager == manager) {
-
-			if (!deregister_unconditionally) {
-				(*pptr)->use_count--;
-			} else {
-				// If we deregister it unconditionally, set use count to 0 so that it will be removed immediately.
-				(*pptr)->use_count = 0;
-			}
-
-			if ((*pptr)->use_count == 0) {
-				dns_push_obj_forget(&(*pptr)->manager);
-				discovered_service_manager_list_item_t *item_to_delete = *pptr;
-				*pptr = (*pptr)->next;
-				mdns_free(item_to_delete);
-			}
+		if ((*pptr)->monitor == monitor) {
 			break;
 		}
+		pptr = &((*pptr)->next);
+	}
+	mdns_require_return(*pptr);
+
+	(*pptr)->use_count--;
+	if ((*pptr)->use_count == 0) {
+		// If no active push question is using this interface monitor, then invalidate it completely.
+		interface_monitor_list_s *if_monitor_to_delete = *pptr;
+		*pptr = (*pptr)->next;
+
+		mdns_interface_monitor_forget(&if_monitor_to_delete->monitor);
+		mdns_free(if_monitor_to_delete);
+	}
+}
+
+//======================================================================================================================
+
+static DNSQuestion *
+_dns_push_discovery_start_srv(mDNS * const m, const mDNSInterfaceID if_id, const dns_push_obj_context_t context)
+{
+	DNSQuestion *srv_q = NULL;
+	mdns_interface_monitor_t if_monitor = NULL;
+	bool failed = true;
+
+	srv_q = mdns_calloc(1, sizeof(*srv_q));
+	mdns_require_quiet(srv_q, exit);
+
+	const dns_obj_domain_name_t push_srv = dns_push_obj_context_get_push_service_name(context);
+	mdns_require_quiet(push_srv, exit);
+
+	const domainname * const qname = (const domainname *)dns_obj_domain_name_get_labels(push_srv);
+	AssignDomainName(&srv_q->qname, qname);
+
+	const DNSQuestion * const original_question = dns_push_obj_context_get_original_question(context);
+	mdns_require_quiet(original_question, exit);
+
+	srv_q->qtype = kDNSServiceType_SRV;
+	srv_q->qclass = original_question->qclass;
+	srv_q->InterfaceID = if_id;
+	srv_q->pid = mDNSPlatformGetPID();
+	srv_q->QuestionCallback = _dns_push_discovery_srv_result_reply;
+	srv_q->QuestionContext = context;
+	srv_q->ReturnIntermed = mDNStrue;
+
+	// Must not set soa_q->LongLived to true because we are in the process of preparing DNS push. Setting it to true
+	// will cause infinite recursive call.
+	srv_q->LongLived = mDNSfalse;
+
+	// To monitor the network change and restart the discovery process when it happens, start interface monitor.
+	const uint32_t if_index = dns_push_obj_context_get_interface_index(context);
+	if_monitor = _dns_push_discovery_start_interface_monitor(if_index);
+	mdns_require_quiet(if_monitor, exit);
+	dns_push_obj_context_set_interface_monitor(context, if_monitor);
+
+	const mStatus q_start_error = mDNS_StartQuery(m, srv_q);
+	mdns_require_noerr_quiet(q_start_error, exit);
+	failed = false;
+
+exit:
+	if (failed) {
+		mdns_free(srv_q);
+	}
+	mdns_forget(&if_monitor);
+	return srv_q;
+}
+
+//======================================================================================================================
+
+static void
+_dns_push_discovery_stop(mDNS * const m, DNSQuestion ** const q_pptr, const dns_push_obj_context_t context,
+	const bool hold_lock)
+{
+	DNSQuestion *q = *q_pptr;
+	if (q != dns_push_obj_context_get_discovery_question(context)) {
+		log_fault("[Q%u] Question being stopped is not the currently active discovery question",
+			mDNSVal16(q->TargetQID));
+	}
+	if (hold_lock) {
+		mDNS_StopQuery(m, q);
+	} else {
+		mDNS_StopQuery_internal(m, q);
+	}
+	mdns_free(q);
+	*q_pptr = mDNSNULL;
+	dns_push_obj_context_set_discovery_question(context, NULL);
+}
+
+//======================================================================================================================
+
+#define kOrphanedPushServiceTimeLimitMs 30 * 1000
+
+static mdns_dns_service_id_t
+_dns_push_discovery_register_push_service(const dns_push_obj_context_t context,
+	const dns_obj_domain_name_t zone, const uint32_t if_index, bool * const out_duplicate,
+	dns_obj_error_t * const out_error)
+{
+	mdns_dns_service_id_t service_id = MDNS_DNS_SERVICE_INVALID_ID;
+	dns_obj_error_t err;
+	dns_obj_domain_name_t dns_push_srv = NULL;
+	mdns_domain_name_t mdns_dns_push_srv = NULL;
+	bool duplicate = false;
+
+	const mdns_dns_service_manager_t service_manager = Querier_GetDNSServiceManager();
+	mdns_require_action_quiet(service_manager, exit, err = DNS_OBJ_ERROR_NO_RESOURCES);
+
+	static dns_obj_domain_name_t dns_push_service_type = NULL;
+	if (!dns_push_service_type) {
+		dns_push_service_type = dns_obj_domain_name_create_with_cstring("_dns-push-tls._tcp", &err);
+		mdns_require_noerr_quiet(err, exit);
+	}
+	dns_push_srv = dns_obj_domain_name_create_concatenation(dns_push_service_type, zone, &err);
+	mdns_require_noerr_quiet(err, exit);
+
+	const uint8_t * const dns_push_srv_labels = dns_obj_domain_name_get_labels(dns_push_srv);
+	mdns_dns_push_srv = mdns_domain_name_create_with_labels(dns_push_srv_labels, NULL);
+	mdns_require_action_quiet(mdns_dns_push_srv, exit, err = DNS_OBJ_ERROR_NO_RESOURCES);
+
+	OSStatus os_err;
+	service_id = mdns_dns_service_manager_register_discovered_push_service(service_manager, mdns_dns_push_srv, if_index,
+		mdns_dns_service_interface_scope_unscoped_and_scoped, kOrphanedPushServiceTimeLimitMs, &duplicate, &os_err);
+	mdns_require_action_quiet(service_id != MDNS_DNS_SERVICE_INVALID_ID, exit, err = (dns_obj_error_t)os_err);
+
+	dns_push_obj_context_set_service_id(context, service_id);
+	if (duplicate) {
+		// If the push service has been registered before, then we need to manually trigger a service change so that
+		// the push question that has finished discovery can start the push connection.
+		Querier_ProcessDNSServiceChangesAsync(mDNStrue);
+	}
+
+exit:
+	mdns_assign(out_duplicate, duplicate);
+	mdns_assign(out_error, err);
+	dns_obj_forget(&dns_push_srv);
+	mdns_forget(&mdns_dns_push_srv);
+	return service_id;
+}
+
+//======================================================================================================================
+
+static void
+_dns_push_discovery_deregister_push_service(const dns_push_obj_context_t context)
+{
+	const mdns_dns_service_id_t service_id = dns_push_obj_context_get_service_id(context);
+	if (service_id != MDNS_DNS_SERVICE_INVALID_ID) {
+		const mdns_dns_service_manager_t service_manager = Querier_GetDNSServiceManager();
+		if (service_manager) {
+			mdns_dns_service_manager_deregister_discovered_push_service(service_manager, service_id);
+			dns_push_obj_context_set_service_id(context, MDNS_DNS_SERVICE_INVALID_ID);
+		}
 	}
 }
 
diff --git a/mDNSMacOSX/dns_push/dns_push_discovery.h b/mDNSMacOSX/dns_push/dns_push_discovery.h
index 764796d..19604b5 100644
--- a/mDNSMacOSX/dns_push/dns_push_discovery.h
+++ b/mDNSMacOSX/dns_push/dns_push_discovery.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2022-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/mDNSMacOSX/dns_push/dns_push_mdns_core.c b/mDNSMacOSX/dns_push/dns_push_mdns_core.c
index f63cbee..2ceb8b0 100644
--- a/mDNSMacOSX/dns_push/dns_push_mdns_core.c
+++ b/mDNSMacOSX/dns_push/dns_push_mdns_core.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2022-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -28,7 +28,9 @@
 #include "dns_push_obj_context.h"
 #include "DNSCommon.h"
 #include "mDNSEmbeddedAPI.h"
+#include "QuerierSupport.h"
 
+#include <os/feature_private.h>
 #include <stdlib.h>
 #include "mdns_strict.h"
 
@@ -38,15 +40,50 @@
 bool
 dns_question_enables_dns_push(const DNSQuestion * const me)
 {
+	// LongLived needs to be enabled to enable DNS push.
+	const mDNSBool dns_push_enabled = me->LongLived;
+	// The question has to come from the client request directly.
+	const mDNSBool client_request = !Querier_QuestionBelongsToSelf(me);
+	// The question name must not end with a local domain.
+	const mDNSBool dot_local_domain = IsLocalDomain(&me->qname);
+	return (dns_push_enabled && client_request && !dot_local_domain);
+}
+
+//======================================================================================================================
+
+bool
+dns_question_uses_dns_push(const DNSQuestion * const me)
+{
 	return (me->dns_push != NULL);
 }
 
 //======================================================================================================================
 
+bool
+dns_question_uses_dns_polling(const DNSQuestion * const me)
+{
+	bool result;
+	mdns_require_action_quiet(dns_question_uses_dns_push(me), exit, result = false);
+
+	const dns_push_obj_context_t context = dns_question_get_dns_push_context(me);
+	mdns_require_action_quiet(context, exit, result = false);
+
+	result = dns_push_obj_context_get_dns_polling_enabled(context);
+
+exit:
+	return result;
+}
+
+//======================================================================================================================
+
 dns_push_obj_context_t
 dns_question_get_dns_push_context(const DNSQuestion * const question)
 {
-	return dns_push_obj_dns_question_member_get_context(question->dns_push);
+	if (question->dns_push) {
+		return dns_push_obj_dns_question_member_get_context(question->dns_push);
+	} else {
+		return NULL;
+	}
 }
 
 //======================================================================================================================
@@ -56,7 +93,7 @@
 {
 	dns_obj_domain_name_t zone = NULL;
 
-	require_quiet(dns_question_enables_dns_push(question), exit);
+	require_quiet(dns_question_uses_dns_push(question), exit);
 
 	const dns_push_obj_context_t context = dns_question_get_dns_push_context(question);
 	require_quiet(context, exit);
@@ -67,4 +104,19 @@
 	return zone;
 }
 
+//======================================================================================================================
+
+bool
+dns_question_finished_push_discovery(const DNSQuestion * const question)
+{
+	bool finished = false;
+	const dns_push_obj_context_t context = dns_question_get_dns_push_context(question);
+	mdns_require_quiet(context, exit);
+
+	finished = (dns_push_obj_context_get_service_id(context) != MDNS_DNS_SERVICE_INVALID_ID);
+
+exit:
+	return finished;
+}
+
 #endif // MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
diff --git a/mDNSMacOSX/dns_push/dns_push_mdns_core.h b/mDNSMacOSX/dns_push/dns_push_mdns_core.h
index b2d191f..6d266c7 100644
--- a/mDNSMacOSX/dns_push/dns_push_mdns_core.h
+++ b/mDNSMacOSX/dns_push/dns_push_mdns_core.h
@@ -37,15 +37,69 @@
 
 __BEGIN_DECLS
 
+/*!
+ *	@brief
+ *		Checks if the DNS question has requested DNS push and is allowed to use DNS push.
+ *
+ *	@param question
+ *		The newly created DNS question.
+ *
+ *	@return
+ *		True if the DNS question has requested DNS push and is allowed to use DNS push.
+ *		Otherwise, false.
+ *
+ *	@discussion
+ *		This function is different from the `dns_question_uses_dns_push` below, which checks if the DNS question is actively doing DNS push.
+ *		This function is used to determine if the caller should start DNS push for the question.
+ */
 bool
 dns_question_enables_dns_push(const DNSQuestion *question);
 
+/*!
+ *	@brief
+ *		Checks if the DNS question is in the progress of doing DNS push.
+ *
+ *	@param question
+ *		The DNS question.
+ *
+ *	@return
+ *		True, if the DNS question is actively doing DNS push. Otherwise, false.
+ */
+bool
+dns_question_uses_dns_push(const DNSQuestion *question);
+
+/*!
+ *	@brief
+ *		Checks if the DNS question tries DNS push but eventually falls back to DNS polling.
+ *
+ *	@param question
+ *		The DNS question.
+ *
+ *	@return
+ *		True, if the DNS question falls back to DNS polling due to no DNS push service availability. Otherwise, false.
+ */
+bool
+dns_question_uses_dns_polling(const DNSQuestion *question);
+
 dns_push_obj_context_t NULLABLE
 dns_question_get_dns_push_context(const DNSQuestion *question);
 
 dns_obj_domain_name_t NULLABLE
 dns_question_get_authoritative_zone(const DNSQuestion *question);
 
+/*!
+ *	@brief
+ *		Check if the current DNS push question has finished the discovery process.
+ *
+ *	@param question
+ *		The DNS question.
+ *
+ *	@result
+ *		True if the discovery process has been finished, otherwise, false.
+ */
+bool
+dns_question_finished_push_discovery(const DNSQuestion *question);
+
 __END_DECLS
 
 NULLABILITY_ASSUME_NONNULL_END
diff --git a/mDNSMacOSX/dns_push/dns_push_objs/dns_push_obj_context.c b/mDNSMacOSX/dns_push/dns_push_objs/dns_push_obj_context.c
index 2739632..34cbd0b 100644
--- a/mDNSMacOSX/dns_push/dns_push_objs/dns_push_obj_context.c
+++ b/mDNSMacOSX/dns_push/dns_push_objs/dns_push_obj_context.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2022-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -26,8 +26,11 @@
 #include "dns_obj_domain_name.h"
 #include "dns_obj_log.h"
 #include "dns_push_obj.h"
+#include "QuerierSupport.h"
 #include "uDNS.h"
 
+#include <mdns/dns_service.h>
+#include <mdns/interface_monitor.h>
 #include <stdint.h>
 #include <stdlib.h>
 
@@ -40,13 +43,14 @@
 
 struct dns_push_obj_context_s {
 	struct ref_count_obj_s						base;					// The reference count and kind support base.
-	dns_obj_rr_soa_t							authority_soa;
-	dns_obj_domain_name_t						authoritative_zone;
-	dns_push_obj_discovered_service_manager_t	service_manager;
-	mDNS *										m;						// The mDNSStorage pointer for the mDNSCore.
+	dns_obj_domain_name_t						authoritative_zone;		// The authoritative zone of the question.
+	dns_obj_domain_name_t						push_service_name;		// The service name of the push service.
+	mdns_dns_service_id_t						service_id;				// The ID of the push service registered.
 	DNSQuestion *								original_question;		// The question that enables DNS push.
-	DNSQuestion *								soa_question;			// The secondary question for authoritative zone
-																		// discovery
+	DNSQuestion *								discovery_question;		// The question for the push service discovery.
+	mdns_interface_monitor_t					interface_monitor;		// The interface monitor for network changes.
+	uint32_t									if_index;				// The interface of the question uses.
+	bool										dns_poll_enabled;		// If DNS polling is enabled for the push query.
 };
 
 DNS_PUSH_OBJECT_DEFINE_FULL(context);
@@ -55,7 +59,7 @@
 // MARK: - DNS Push Question Context Public Methods
 
 dns_push_obj_context_t
-dns_push_obj_context_create(mDNS *const m, DNSQuestion * const q, dns_obj_error_t * const out_error)
+dns_push_obj_context_create(DNSQuestion * const q, dns_obj_error_t * const out_error)
 {
 	dns_obj_error_t err;
 	dns_obj_domain_name_t q_name = NULL;
@@ -69,14 +73,14 @@
 
 	const bool is_single_label = (dns_obj_domain_name_is_single_label(q_name));
 	require_action(!is_single_label, exit,
-		log_error("Unable to start DNS push server discovery for the single-label name (TLD) -- "
-			"qname: " PRI_DNS_DM_NAME ", qtype: " PUB_DNS_TYPE, DNS_DM_NAME_PARAM(q_name), DNS_TYPE_PARAM(q_type));
+		log_error("[Q%u] Unable to start DNS push server discovery for the single-label name (TLD) -- "
+			"qname: " PRI_DNS_DM_NAME ", qtype: " PUB_DNS_TYPE, mDNSVal16(q->TargetQID), DNS_DM_NAME_PARAM(q_name),
+			DNS_TYPE_PARAM(q_type));
 			err = DNS_OBJ_ERROR_PARAM_ERR);
 
 	obj = _dns_push_obj_context_new();
 	require_action(obj != NULL, exit, err = DNS_OBJ_ERROR_NO_MEMORY);
 
-	obj->m = m;
 	obj->original_question = q;
 
 	context = obj;
@@ -93,17 +97,17 @@
 //======================================================================================================================
 
 void
-dns_push_obj_context_set_soa_question(const dns_push_obj_context_t me, DNSQuestion * const soa_question)
+dns_push_obj_context_set_discovery_question(const dns_push_obj_context_t me, DNSQuestion * const question)
 {
-	me->soa_question = soa_question;
+	me->discovery_question = question;
 }
 
 //======================================================================================================================
 
 DNSQuestion *
-dns_push_obj_context_get_soa_question(const dns_push_obj_context_t me)
+dns_push_obj_context_get_discovery_question(const dns_push_obj_context_t me)
 {
-	return me->soa_question;
+	return me->discovery_question;
 }
 
 //======================================================================================================================
@@ -132,19 +136,95 @@
 
 //======================================================================================================================
 
-void
-dns_push_obj_context_set_service_manager(const dns_push_obj_context_t me,
-	const dns_push_obj_discovered_service_manager_t manager)
+dns_obj_domain_name_t
+dns_push_obj_context_get_push_service_name(const dns_push_obj_context_t me)
 {
-	dns_push_obj_replace(&me->service_manager, manager);
+	dns_obj_error_t err;
+	dns_obj_domain_name_t dns_push_service_type = NULL;
+	dns_obj_domain_name_t dns_push_srv = NULL;
+
+	if (me->push_service_name) {
+		goto exit;
+	}
+	if (!me->authoritative_zone) {
+		goto exit;
+	}
+	dns_push_service_type = dns_obj_domain_name_create_with_cstring("_dns-push-tls._tcp", &err);
+	mdns_require_noerr_quiet(err, exit);
+
+	dns_push_srv = dns_obj_domain_name_create_concatenation(dns_push_service_type, me->authoritative_zone, &err);
+	mdns_require_noerr_quiet(err, exit);
+
+	dns_obj_replace(&me->push_service_name, dns_push_srv);
+
+exit:
+	dns_obj_forget(&dns_push_service_type);
+	dns_obj_forget(&dns_push_srv);
+	return me->push_service_name;
 }
 
 //======================================================================================================================
 
-dns_push_obj_discovered_service_manager_t
-dns_push_obj_context_get_service_manager(const dns_push_obj_context_t me)
+void
+dns_push_obj_context_set_service_id(const dns_push_obj_context_t me, const mdns_dns_service_id_t service_id)
 {
-	return me->service_manager;
+	me->service_id = service_id;
+}
+
+//======================================================================================================================
+
+mdns_dns_service_id_t
+dns_push_obj_context_get_service_id(const dns_push_obj_context_t me)
+{
+	return me->service_id;
+}
+
+//======================================================================================================================
+
+void
+dns_push_obj_context_set_interface_index(const dns_push_obj_context_t me, const uint32_t if_index)
+{
+	me->if_index = if_index;
+}
+
+//======================================================================================================================
+
+uint32_t
+dns_push_obj_context_get_interface_index(const dns_push_obj_context_t me)
+{
+	return me->if_index;
+}
+
+//======================================================================================================================
+
+void
+dns_push_obj_context_set_interface_monitor(const dns_push_obj_context_t me, const mdns_interface_monitor_t monitor)
+{
+	mdns_replace(&me->interface_monitor, monitor);
+}
+
+//======================================================================================================================
+
+mdns_interface_monitor_t
+dns_push_obj_context_get_interface_monitor(const dns_push_obj_context_t me)
+{
+	return me->interface_monitor;
+}
+
+//======================================================================================================================
+
+void
+dns_push_obj_context_set_dns_polling_enabled(const dns_push_obj_context_t me, const bool dns_polling_enabled)
+{
+	me->dns_poll_enabled = dns_polling_enabled;
+}
+
+//======================================================================================================================
+
+bool
+dns_push_obj_context_get_dns_polling_enabled(const dns_push_obj_context_t me)
+{
+	return me->dns_poll_enabled;
 }
 
 //======================================================================================================================
@@ -162,8 +242,15 @@
 static void
 _dns_push_obj_context_finalize(const dns_push_obj_context_t me)
 {
-	dns_obj_forget(&me->authority_soa);
 	dns_obj_forget(&me->authoritative_zone);
+	dns_obj_forget(&me->push_service_name);
+	mdns_forget(&me->interface_monitor);
+	if (me->service_id != MDNS_DNS_SERVICE_INVALID_ID) {
+		const mdns_dns_service_manager_t manager = Querier_GetDNSServiceManager();
+		if (manager) {
+			mdns_dns_service_manager_deregister_discovered_push_service(manager, me->service_id);
+		}
+	}
 }
 
 #endif // MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
diff --git a/mDNSMacOSX/dns_push/dns_push_objs/dns_push_obj_context.h b/mDNSMacOSX/dns_push/dns_push_objs/dns_push_obj_context.h
index bebc688..94d0e70 100644
--- a/mDNSMacOSX/dns_push/dns_push_objs/dns_push_obj_context.h
+++ b/mDNSMacOSX/dns_push/dns_push_objs/dns_push_obj_context.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2022-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -26,7 +26,6 @@
 #include "dns_common.h"
 #include "dns_obj_rr_soa.h"
 #include "dns_push_obj.h"
-#include "dns_push_obj_discovered_service_manager.h"
 #include "general.h"
 #include "mDNSEmbeddedAPI.h"
 #include "nullability.h"
@@ -47,13 +46,13 @@
 __BEGIN_DECLS
 
 dns_push_obj_context_t NULLABLE
-dns_push_obj_context_create(mDNS *m, DNSQuestion *q, dns_obj_error_t * NULLABLE out_error);
+dns_push_obj_context_create(DNSQuestion *q, dns_obj_error_t * NULLABLE out_error);
 
 void
-dns_push_obj_context_set_soa_question(dns_push_obj_context_t context, DNSQuestion * NULLABLE soa_question);
+dns_push_obj_context_set_discovery_question(dns_push_obj_context_t context, DNSQuestion * NULLABLE question);
 
 DNSQuestion *
-dns_push_obj_context_get_soa_question(dns_push_obj_context_t context);
+dns_push_obj_context_get_discovery_question(dns_push_obj_context_t context);
 
 DNSQuestion *
 dns_push_obj_context_get_original_question(dns_push_obj_context_t context);
@@ -64,12 +63,32 @@
 dns_obj_domain_name_t NULLABLE
 dns_push_obj_context_get_authoritative_zone(dns_push_obj_context_t context);
 
-void
-dns_push_obj_context_set_service_manager(dns_push_obj_context_t context,
-	dns_push_obj_discovered_service_manager_t NULLABLE manager);
+dns_obj_domain_name_t NULLABLE
+dns_push_obj_context_get_push_service_name(dns_push_obj_context_t context);
 
-dns_push_obj_discovered_service_manager_t NULLABLE
-dns_push_obj_context_get_service_manager(dns_push_obj_context_t context);
+void
+dns_push_obj_context_set_service_id(dns_push_obj_context_t context, mdns_dns_service_id_t service_id);
+
+mdns_dns_service_id_t
+dns_push_obj_context_get_service_id(dns_push_obj_context_t context);
+
+void
+dns_push_obj_context_set_interface_index(dns_push_obj_context_t context, uint32_t if_index);
+
+uint32_t
+dns_push_obj_context_get_interface_index(dns_push_obj_context_t context);
+
+void
+dns_push_obj_context_set_interface_monitor(dns_push_obj_context_t context, mdns_interface_monitor_t NULLABLE monitor);
+
+mdns_interface_monitor_t
+dns_push_obj_context_get_interface_monitor(dns_push_obj_context_t context);
+
+void
+dns_push_obj_context_set_dns_polling_enabled(dns_push_obj_context_t context, bool dns_polling_enabled);
+
+bool
+dns_push_obj_context_get_dns_polling_enabled(dns_push_obj_context_t context);
 
 __END_DECLS
 
diff --git a/mDNSMacOSX/dns_push/dns_push_objs/dns_push_obj_discovered_service_manager.c b/mDNSMacOSX/dns_push/dns_push_objs/dns_push_obj_discovered_service_manager.c
deleted file mode 100644
index 87a9cce..0000000
--- a/mDNSMacOSX/dns_push/dns_push_objs/dns_push_obj_discovered_service_manager.c
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Copyright (c) 2023 Apple Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#include "mDNSFeatures.h"
-#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
-
-//======================================================================================================================
-// MARK: - Headers
-
-#include "dns_push_obj_discovered_service_manager.h"
-
-#include "dns_service.h"
-#include "dns_obj_log.h"
-#include "dns_push_obj.h"
-#include "QuerierSupport.h"
-
-#include "mdns_strict.h"
-
-//======================================================================================================================
-// MARK: - DNS Push Discovered Service Manager Kind Definition
-
-struct dns_push_obj_discovered_service_manager_s {
-	struct ref_count_obj_s	base;	// The reference count and kind support base.
-	dns_obj_domain_name_t	discovered_authoritative_zone;
-	uint32_t				if_index;
-	mdns_dns_service_id_t	service_id;
-};
-
-DNS_PUSH_OBJECT_DEFINE_WITH_INIT_WITHOUT_COMPARATOR(discovered_service_manager);
-
-//======================================================================================================================
-// MARK: - DNS Push Discovered Service Manager Public Methods
-
-dns_push_obj_discovered_service_manager_t
-dns_push_obj_discovered_service_manager_create(const dns_obj_domain_name_t discovered_authoritative_zone,
-	const uint32_t if_index, dns_obj_error_t * const out_error)
-{
-	dns_obj_error_t err;
-	dns_obj_domain_name_t dns_push_srv = NULL;
-	mdns_domain_name_t mdns_dns_push_srv = NULL;
-	dns_push_obj_discovered_service_manager_t manager = NULL;
-	dns_push_obj_discovered_service_manager_t object = NULL;
-
-	static dns_obj_domain_name_t dns_push_service_type = NULL;
-	if (!dns_push_service_type) {
-		dns_push_service_type = dns_obj_domain_name_create_with_cstring("_dns-push-tls._tcp", &err);
-		mdns_require_noerr_quiet(err, exit);
-	}
-	mdns_require_action_quiet(dns_push_service_type, exit, err = DNS_OBJ_ERROR_UNEXPECTED_ERR);
-
-	object = _dns_push_obj_discovered_service_manager_new();
-	mdns_require_action_quiet(object, exit, err = DNS_OBJ_ERROR_NO_RESOURCES);
-
-	object->discovered_authoritative_zone = discovered_authoritative_zone;
-	dns_obj_retain(object->discovered_authoritative_zone);
-	object->if_index = if_index;
-
-	const mdns_dns_service_manager_t mdns_manager = Querier_GetDNSServiceManager();
-	mdns_require_action_quiet(mdns_manager, exit, err = DNS_OBJ_ERROR_NO_RESOURCES);
-
-	dns_push_srv = dns_obj_domain_name_create_concatenation(dns_push_service_type, discovered_authoritative_zone, &err);
-	mdns_require_noerr_quiet(err, exit);
-
-	const uint8_t * const dns_push_srv_labels = dns_obj_domain_name_get_labels(dns_push_srv);
-	mdns_dns_push_srv = mdns_domain_name_create_with_labels(dns_push_srv_labels, NULL);
-	mdns_require_action_quiet(mdns_dns_push_srv, exit, err = DNS_OBJ_ERROR_NO_RESOURCES);
-
-	mdns_dns_service_t service = mdns_dns_service_manager_get_push_service(mdns_manager, mdns_dns_push_srv, if_index);
-	mdns_require_action_quiet(!service, exit, err = DNS_OBJ_ERROR_ALREADY_INITIALIZED_ERR);
-
-	const mdns_dns_service_id_t service_id = mdns_dns_service_manager_register_push_service(mdns_manager,
-		mdns_dns_push_srv, if_index, NULL);
-	mdns_require_action_quiet(service_id != MDNS_DNS_SERVICE_INVALID_ID, exit, err = DNS_OBJ_ERROR_UNEXPECTED_ERR);
-
-	object->service_id = service_id;
-	dns_push_obj_replace(&manager, object);
-	err = DNS_OBJ_ERROR_NO_ERROR;
-
-exit:
-	dns_obj_forget(&dns_push_srv);
-	mdns_forget(&mdns_dns_push_srv);
-	dns_push_obj_forget(&object);
-	mdns_assign(out_error, err);
-	return manager;
-}
-
-//======================================================================================================================
-
-bool
-dns_push_obj_discovered_service_manager_manages_this_zone(dns_push_obj_discovered_service_manager_t me,
-	const dns_obj_domain_name_t discovered_authoritative_zone, const uint32_t if_index)
-{
-	return (dns_obj_equal(me->discovered_authoritative_zone, discovered_authoritative_zone)
-			&& me->if_index == if_index);
-}
-
-//======================================================================================================================
-// MARK: - DNS Push Discovered Service Manager Private Methods
-
-static void
-_dns_push_obj_discovered_service_manager_initialize(dns_push_obj_discovered_service_manager_t me)
-{
-	me->service_id = MDNS_DNS_SERVICE_INVALID_ID;
-}
-
-//======================================================================================================================
-
-static void
-_dns_push_obj_discovered_service_manager_finalize(dns_push_obj_discovered_service_manager_t me)
-{
-	dns_obj_forget(&me->discovered_authoritative_zone);
-	if (me->service_id != MDNS_DNS_SERVICE_INVALID_ID) {
-		const mdns_dns_service_manager_t mdns_manager = Querier_GetDNSServiceManager();
-		mdns_require_return(mdns_manager);
-
-		mdns_dns_service_manager_deregister_native_service(mdns_manager, me->service_id);
-	}
-}
-
-#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
diff --git a/mDNSMacOSX/dns_push/dns_push_objs/dns_push_obj_discovered_service_manager.h b/mDNSMacOSX/dns_push/dns_push_objs/dns_push_obj_discovered_service_manager.h
deleted file mode 100644
index 860066d..0000000
--- a/mDNSMacOSX/dns_push/dns_push_objs/dns_push_obj_discovered_service_manager.h
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (c) 2023 Apple Inc. All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef DNS_PUSH_OBJ_DISCOVERED_SERVICE_MANAGER_H
-#define DNS_PUSH_OBJ_DISCOVERED_SERVICE_MANAGER_H
-
-#include "mDNSFeatures.h"
-#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
-
-//======================================================================================================================
-// MARK: - Headers
-
-#include "dns_obj_domain_name.h"
-#include "dns_push_obj.h"
-#include "general.h"
-
-//======================================================================================================================
-// MARK: - Object Reference Definition
-
-DNS_PUSH_OBJECT_TYPEDEF_OPAQUE_POINTER(discovered_service_manager);
-
-//======================================================================================================================
-// MARK: - Object Methods
-
-NULLABILITY_ASSUME_NONNULL_BEGIN
-
-__BEGIN_DECLS
-
-dns_push_obj_discovered_service_manager_t NULLABLE
-dns_push_obj_discovered_service_manager_create(dns_obj_domain_name_t discovered_authoritative_zone, uint32_t if_index,
-	dns_obj_error_t * NULLABLE out_error);
-
-bool
-dns_push_obj_discovered_service_manager_manages_this_zone(dns_push_obj_discovered_service_manager_t manager,
-	dns_obj_domain_name_t discovered_authoritative_zone, uint32_t if_index);
-
-__END_DECLS
-
-NULLABILITY_ASSUME_NONNULL_END
-
-#endif // MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
-
-#endif // DNS_PUSH_OBJ_DISCOVERED_SERVICE_MANAGER_H
diff --git a/mDNSMacOSX/dnsproxy.c b/mDNSMacOSX/dnsproxy.c
index d7ade96..fd21570 100644
--- a/mDNSMacOSX/dnsproxy.c
+++ b/mDNSMacOSX/dnsproxy.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2011-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2011-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -378,6 +378,7 @@
             //   will also be returned. If the client is explicitly looking up
             //   a DNSSEC record (e.g., DNSKEY, DS) we should return the response.
             //   DNSSECOK bit only influences whether we add the RRSIG or not.
+            mDNSBool addedCNAMERecord = mDNSfalse;
             if (cr->resrec.RecordType != kDNSRecordTypePacketNegative)
             {
                 const ResourceRecord *rr;
@@ -422,6 +423,10 @@
                 }
                 len += (ptr - pc->omsg_ptr);
                 pc->omsg_ptr = ptr;
+                if (cr->resrec.rrtype == kDNSType_CNAME)
+                {
+                    addedCNAMERecord = mDNStrue;
+                }
             }
             if (cr->soa)
             {
@@ -436,6 +441,16 @@
             {
                 LogInfo("AddResourceRecord: cname set for %s", CRDisplayString(m ,cr));
             }
+            // If a CNAME record was just added to the response for the current QNAME, break out of the for-loop.
+            // As described in <https://datatracker.ietf.org/doc/html/rfc2181#section-10.1>, there cannot be more
+            // than one CNAME record for a given domain name. This is a defensive measure because because it's
+            // currently possible for two CNAME records with the same name to end up in mDNSResponder's cache.
+            // To avoid "garbage in, garbage out", which can confuse clients, the DNS proxy shouldn't create
+            // responses with multiple CNAME records with the same name. See rdar://117823387 for more details.
+            if (addedCNAMERecord)
+            {
+                break;
+            }
         }
     }
     // Along with the nsec records, we also cache the SOA record. For non-DNSSEC question, we need
@@ -959,60 +974,6 @@
     return err;
 }
 
-static mrcs_dns_proxy_t gLegacyProxy = mDNSNULL;
-
-#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PROXY_DNS64)
-mDNSexport void DNSProxyInit(mDNSu32 IpIfArr[MaxIp], mDNSu32 OpIf, const mDNSu8 IPv6Prefix[16], int IPv6PrefixBitLen,
-                             mDNSBool forceAAAASynthesis)
-#else
-mDNSexport void DNSProxyInit(mDNSu32 IpIfArr[MaxIp], mDNSu32 OpIf)
-#endif
-{
-    if (gLegacyProxy)
-    {
-        return;
-    }
-    gLegacyProxy = mrcs_dns_proxy_create(NULL);
-    if (!gLegacyProxy)
-    {
-        return;
-    }
-    for (int i = 0; i < MaxIp; ++i)
-    {
-        mrcs_dns_proxy_add_input_interface(gLegacyProxy, IpIfArr[i]);
-    }
-    mrcs_dns_proxy_set_output_interface(gLegacyProxy, OpIf);
-
-#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PROXY_DNS64)
-    if (IPv6Prefix)
-    {
-        const OSStatus err = mrcs_dns_proxy_set_nat64_prefix(gLegacyProxy, IPv6Prefix, IPv6PrefixBitLen);
-        if (!err)
-        {
-            mrcs_dns_proxy_enable_force_aaaa_synthesis(gLegacyProxy, forceAAAASynthesis ? true : false);
-            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
-                "DNSProxy using DNS64 IPv6 prefix: " PRI_IPv6_ADDR "/%d" PUB_S,
-                IPv6Prefix, IPv6PrefixBitLen, forceAAAASynthesis ? "" : " (force AAAA synthesis)");
-        }
-        else
-        {
-            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
-                "DNSProxy not using invalid DNS64 IPv6 prefix: " PRI_IPv6_ADDR "/%d", IPv6Prefix, IPv6PrefixBitLen);
-        }
-    }
-#endif
-    DNSProxyStart(gLegacyProxy);
-}
-
-mDNSexport void DNSProxyTerminate(void)
-{
-    if (gLegacyProxy)
-    {
-        DNSProxyStop(gLegacyProxy);
-        mrcs_forget(&gLegacyProxy);
-    }
-}
-
 mDNSlocal OSStatus DNSProxyStartHandler(const mrcs_dns_proxy_t proxy)
 {
     KQueueLock();
@@ -1051,11 +1012,11 @@
     return state;
 }
 
-const struct mrcs_server_handlers_s kMRCSServerHandlers =
+const struct mrcs_server_dns_proxy_handlers_s kMRCSServerDNSProxyHandlers =
 {
-    .dns_proxy_start = DNSProxyStartHandler,
-    .dns_proxy_stop = DNSProxyStopHandler,
-    .dns_proxy_get_state = DNSProxyGetStateHandler
+    .start = DNSProxyStartHandler,
+    .stop = DNSProxyStopHandler,
+    .get_state = DNSProxyGetStateHandler
 };
 
 #else // UNICAST_DISABLED
diff --git a/mDNSMacOSX/dnsproxy.h b/mDNSMacOSX/dnsproxy.h
index abecc4d..e2eb10b 100644
--- a/mDNSMacOSX/dnsproxy.h
+++ b/mDNSMacOSX/dnsproxy.h
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2011-2021 Apple Inc. All rights reserved.
+ * Copyright (c) 2011-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -24,15 +24,8 @@
 extern void ProxyUDPCallback(void *socket, DNSMessage *const msg, const mDNSu8 *const end, const mDNSAddr *const srcaddr,
                              const mDNSIPPort srcport, const mDNSAddr *dstaddr, const mDNSIPPort dstport, const mDNSInterfaceID InterfaceID, void *context);
 extern void ProxyTCPCallback(void *socket, DNSMessage *const msg, const mDNSu8 *const end, const mDNSAddr *const srcaddr,
-                             const mDNSIPPort srcport, const mDNSAddr *dstaddr, const mDNSIPPort dstport, const mDNSInterfaceID InterfaceID, void *context);                          
-#if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PROXY_DNS64)
-extern void DNSProxyInit(mDNSu32 IpIfArr[MaxIp], mDNSu32 OpIf, const mDNSu8 IPv6Prefix[16], int IPv6PrefixLen,
-                         mDNSBool alwaysSynthesize);
-#else
-extern void DNSProxyInit(mDNSu32 IpIfArr[MaxIp], mDNSu32 OpIf);
-#endif
-extern void DNSProxyTerminate(void);
+                             const mDNSIPPort srcport, const mDNSAddr *dstaddr, const mDNSIPPort dstport, const mDNSInterfaceID InterfaceID, void *context);
 
-extern const struct mrcs_server_handlers_s kMRCSServerHandlers;
+extern const struct mrcs_server_dns_proxy_handlers_s kMRCSServerDNSProxyHandlers;
 
 #endif // __DNS_PROXY_H
diff --git a/mDNSMacOSX/dnssd.c b/mDNSMacOSX/dnssd.c
index b4148d1..8d18951 100644
--- a/mDNSMacOSX/dnssd.c
+++ b/mDNSMacOSX/dnssd.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2019-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -50,26 +50,28 @@
 	check_compile_time(sizeof_field(DNSSD_STRUCT(NAME), base) == sizeof(DNSSD_STRUCT(SUPER)));			\
 	extern int _dnssd_base_type_check[sizeof(&(((DNSSD_TYPE(NAME))0)->base) == ((DNSSD_TYPE(SUPER))0))]
 
-#define DNSSD_KIND_DEFINE(NAME, SUPER) 											\
-	static const struct dnssd_kind_s _dnssd_ ## NAME ## _kind = {				\
-		&_dnssd_ ## SUPER ## _kind,												\
-		# NAME,																	\
-		_dnssd_ ## NAME ## _copy_description,									\
-		_dnssd_ ## NAME ## _finalize,											\
-	};																			\
-																				\
-	static DNSSD_TYPE(NAME)														\
-	_dnssd_ ## NAME ## _alloc(void)												\
-	{																			\
-		DNSSD_TYPE(NAME) obj = dnssd_object_ ## NAME ## _alloc(sizeof(*obj));	\
-		require_quiet(obj, exit);												\
-																				\
-		const dnssd_object_t base = (dnssd_object_t)obj;						\
-		base->kind = &_dnssd_ ## NAME ## _kind;									\
-																				\
-	exit:																		\
-		return obj;																\
-	}																			\
+#define DNSSD_KIND_DEFINE(NAME, SUPER) 													\
+	static const struct dnssd_kind_s _dnssd_ ## NAME ## _kind = {						\
+		MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_BEGIN()	\
+		&_dnssd_ ## SUPER ## _kind,														\
+		# NAME,																			\
+		_dnssd_ ## NAME ## _copy_description,											\
+		_dnssd_ ## NAME ## _finalize,													\
+		MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_END()		\
+	};																					\
+																						\
+	static DNSSD_TYPE(NAME)																\
+	_dnssd_ ## NAME ## _alloc(void)														\
+	{																					\
+		DNSSD_TYPE(NAME) obj = dnssd_object_ ## NAME ## _alloc(sizeof(*obj));			\
+		require_quiet(obj, exit);														\
+																						\
+		const dnssd_object_t base = (dnssd_object_t)obj;								\
+		base->kind = &_dnssd_ ## NAME ## _kind;											\
+																						\
+	exit:																				\
+		return obj;																		\
+	}																					\
 	DNSSD_BASE_CHECK(NAME, SUPER)
 
 DNSSD_KIND_DECLARE(getaddrinfo);
@@ -1034,7 +1036,7 @@
 		if (privacy) {
 			addr_str = DNSSD_REDACTED_IPv4_ADDRESS_STR;
 		} else {
-			check_compile_time_code(sizeof(addr_buf) >= INET_ADDRSTRLEN);
+			mdns_compile_time_check_local(sizeof(addr_buf) >= INET_ADDRSTRLEN);
 			addr_str = inet_ntop(AF_INET, &me->addr.v4.sin_addr.s_addr, addr_buf, (socklen_t)sizeof(addr_buf));
 		}
 	} else if (me->addr.sa.sa_family == AF_INET6) {
@@ -1042,7 +1044,7 @@
 			addr_str = DNSSD_REDACTED_IPv6_ADDRESS_STR;
 		} else {
 			const struct sockaddr_in6 * const sin6 = &me->addr.v6;
-			check_compile_time_code(sizeof(addr_buf) >= INET6_ADDRSTRLEN);
+			mdns_compile_time_check_local(sizeof(addr_buf) >= INET6_ADDRSTRLEN);
 			addr_str = inet_ntop(AF_INET6, sin6->sin6_addr.s6_addr, addr_buf, (socklen_t)sizeof(addr_buf));
 			if (addr_str && (sin6->sin6_scope_id > 0)) {
 				char * const		dst = &addr_buf[strlen(addr_buf)];
@@ -1851,10 +1853,8 @@
 		case dnssd_getaddrinfo_result_type_expired:
 			break;
 
-		CUClangWarningIgnoreBegin(-Wcovered-switch-default);
-		case dnssd_getaddrinfo_result_type_service_binding: CU_ATTRIBUTE_FALLTHROUGH;
-		default:
-		CUClangWarningIgnoreEnd();
+		case dnssd_getaddrinfo_result_type_service_binding:
+		MDNS_COVERED_SWITCH_DEFAULT:
 			err = kTypeErr;
 			goto exit;
 	}
diff --git a/mDNSMacOSX/dnssd_analytics.c b/mDNSMacOSX/dnssd_analytics.c
index 41f94c5..954279b 100644
--- a/mDNSMacOSX/dnssd_analytics.c
+++ b/mDNSMacOSX/dnssd_analytics.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2019-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -55,7 +55,7 @@
 	qtype_count_t 		v6;
 	qtype_count_t 		https;
 } dns_analytic_t;
-static dns_analytic_t s_dns_analytics[e_index_count] = {};
+static dns_analytic_t s_dns_analytics[e_index_count] = {0};
 
 // MARK:  - Private Functions
 
@@ -69,8 +69,9 @@
 		case e_index_encrypted_noncell:
 		case e_index_encrypted_cell:
 			return "encrypted";
+		default:
+			return NULL;
 	}
-	return NULL;
 }
 
 static inline const char *
@@ -83,8 +84,9 @@
 		case e_index_standard_cell:
 		case e_index_encrypted_cell:
 			return "cellular";
+		default:
+			return NULL;
 	}
-	return NULL;
 }
 
 static void
@@ -142,6 +144,9 @@
 		case kDNSType_HTTPS:
 			qtype_ptr = &analytic->https;
 			break;
+
+		default:
+			break;
 	}
 	return qtype_ptr;
 }
@@ -232,6 +237,7 @@
 dnssd_analytics_dns_transport_for_resolver_type(mdns_resolver_type_t type)
 {
 	dns_transport_t transport;
+	mdns_clang_ignore_warning_begin(-Wswitch-default);
 	switch (type) {
 		case mdns_resolver_type_https:
 			transport = dns_transport_DoH;
@@ -250,6 +256,7 @@
 			transport = dns_transport_Undefined;
 			break;
 	}
+	mdns_clang_ignore_warning_end();
 	return transport;
 }
 
diff --git a/mDNSMacOSX/dnssd_server.c b/mDNSMacOSX/dnssd_server.c
index 932d25f..d55e13a 100644
--- a/mDNSMacOSX/dnssd_server.c
+++ b/mDNSMacOSX/dnssd_server.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2019-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -58,6 +58,12 @@
 #include <mdns/signed_result.h>
 #endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
+#include "uds_daemon.h"
+#include <mdns/dispatch.h>
+#include <mdns/powerlog.h>
+#endif
+
 #include "mdns_strict.h"
 
 //======================================================================================================================
@@ -77,11 +83,13 @@
 	check_compile_time(sizeof_field(DX_STRUCT(NAME), base) == sizeof(DX_STRUCT(SUPER)));	\
 	extern int _dx_base_type_check[sizeof(&(((dx_ ## NAME ## _t)0)->base) == ((dx_ ## SUPER ## _t)0))]
 
-#define DX_SUBKIND_DEFINE_ABSTRACT(NAME, SUPER, ...)		\
-	static const struct dx_kind_s _dx_ ## NAME ## _kind = {	\
-		.superkind = &_dx_ ## SUPER ##_kind,				\
-		__VA_ARGS__											\
-	};														\
+#define DX_SUBKIND_DEFINE_ABSTRACT(NAME, SUPER, ...)									\
+	static const struct dx_kind_s _dx_ ## NAME ## _kind = {								\
+		MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_BEGIN()	\
+		.superkind = &_dx_ ## SUPER ##_kind,											\
+		__VA_ARGS__																		\
+		MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_END()		\
+	};																					\
 	DX_BASE_CHECK(NAME, SUPER)
 
 #define DX_SUBKIND_DEFINE(NAME, SUPER, ...)												\
@@ -255,6 +263,9 @@
 struct dx_gai_request_s {
 	struct dx_request_s				base;					// Request object base.
 	mdns_dns_service_id_t			custom_service_id;		// ID for this request's custom DNS service.
+#if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
+	uint64_t						powerlog_start_time;	// If non-zero, time when mDNS client request started.
+#endif
 	GetAddrInfoClientRequest *		gai;					// Underlying GAI request.
 	QueryRecordClientRequest *		query;					// Underlying SVCB/HTTPS query request.
 	dx_gai_result_t					results;				// List of pending results.
@@ -292,7 +303,7 @@
 	MDNS_STRUCT_PAD_64_32(1, 1);
 };
 MDNS_CLANG_TREAT_WARNING_AS_ERROR_END()
-mdns_compile_time_max_size_check(struct dx_gai_request_s, 248);
+mdns_compile_time_max_size_check(struct dx_gai_request_s, 256);
 
 // Notes:
 // 1. If a client request specifies that DNS services that allow failover be avoided if they're unable to provide
@@ -340,6 +351,9 @@
 typedef xpc_object_t
 (*dx_request_take_results_f)(dx_any_request_t request);
 
+typedef void
+(*dx_request_report_powerlog_progress_f)(dx_any_request_t request);
+
 typedef const struct dx_request_kind_s * dx_request_kind_t;
 struct dx_request_kind_s {
 	struct dx_kind_s			base;
@@ -354,12 +368,14 @@
 	_dx_ ## NAME ## _request_finalize(dx_ ## NAME ## _request_t request);							\
 																									\
 	static const struct dx_request_kind_s _dx_ ## NAME ## _request_kind = {							\
+		MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_BEGIN()				\
 		.base = {																					\
 			.superkind	= &_dx_request_kind,														\
 			.invalidate	= _dx_ ## NAME ## _request_invalidate,										\
 			.finalize	= _dx_ ## NAME ## _request_finalize											\
 		},																							\
 		__VA_ARGS__																					\
+		MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_END()					\
 	};																								\
 																									\
 	static dx_ ## NAME ## _request_t																\
@@ -377,7 +393,7 @@
 _dx_gai_request_take_results(dx_gai_request_t request);
 
 DX_REQUEST_SUBKIND_DEFINE(gai,
-	.take_results	= _dx_gai_request_take_results
+	.take_results = _dx_gai_request_take_results,
 );
 
 //======================================================================================================================
@@ -609,9 +625,11 @@
 mDNSexport void
 dnssd_server_init(void)
 {
-	static dispatch_once_t	s_once = 0;
-	static xpc_connection_t	s_listener = NULL;
-
+	static dispatch_once_t s_once = 0;
+	static xpc_connection_t s_listener = NULL;
+#if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
+	static dispatch_source_t s_powerlog_progress_timer = NULL;
+#endif
 	dispatch_once(&s_once,
 	^{
 		s_listener = xpc_connection_create_mach_service(DNSSD_MACH_SERVICE_NAME, _dx_server_queue(),
@@ -624,6 +642,23 @@
 			}
 		});
 		xpc_connection_activate(s_listener);
+	#if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
+		const uint32_t interval_ms = 30 * MDNS_MILLISECONDS_PER_MINUTE;
+		s_powerlog_progress_timer = mdns_dispatch_create_periodic_monotonic_timer(interval_ms, 5, _dx_server_queue());
+		if (s_powerlog_progress_timer) {
+			dispatch_source_set_event_handler(s_powerlog_progress_timer,
+			^{
+				os_log_debug(_mdns_server_log(), "periodic powerlog report timer fired");
+				_dx_kqueue_locked("dnssd_server: submitting client summary to powerlog", true,
+				^{
+					mdns_powerlog_submit_client_summary();
+				});
+			});
+			dispatch_activate(s_powerlog_progress_timer);
+		} else {
+			os_log_fault(_mdns_server_log(), "Failed to create periodic powerlog report timer");
+		}
+	#endif
 	});
 }
 
@@ -1442,9 +1477,7 @@
 			err = kDNSServiceErr_NoAuth;
 			break;
 
-		CUClangWarningIgnoreBegin(-Wcovered-switch-default);
-		default:
-		CUClangWarningIgnoreEnd();
+		MDNS_COVERED_SWITCH_DEFAULT:
 			err = kDNSServiceErr_Unknown;
 			break;
 	}
@@ -1471,7 +1504,7 @@
 	}
 #endif
 #if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
-	mdns_trust_forget(&me->trust);
+	mdns_trust_forget_with_invalidation(&me->trust);
 #endif
 #if MDNSRESPONDER_SUPPORTS(APPLE, SIGNED_RESULTS)
 	mdns_forget(&me->signed_resolve);
@@ -1750,6 +1783,16 @@
 		if (gai_params && !me->gai) {
 			me->gai = _dx_get_addr_info_client_request_start(gai_params, _dx_gai_request_gai_result_handler, me, &err);
 			require_noerr_return(err);
+		#if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
+			if (me->gai) {
+				const domainname *const qname = GetAddrInfoClientRequestGetQName(me->gai);
+				if ((me->ifindex != kDNSServiceInterfaceIndexLocalOnly) && IsLocalDomain(qname)) {
+					const char * const client_name = me->base.session->client_name;
+					const mDNSBool uses_awdl = ClientRequestUsesAWDL(me->ifindex, me->flags);
+					me->powerlog_start_time = mdns_powerlog_getaddrinfo_start(client_name, uses_awdl);
+				}
+			}
+		#endif
 		}
 	});
 	if (err) {
@@ -1766,6 +1809,14 @@
 	_dx_kqueue_locked("dx_gai_request: stopping client requests", need_lock,
 	^{
 		_dx_get_addr_info_client_request_forget(&me->gai);
+	#if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
+		if (me->powerlog_start_time != 0) {
+			const char * const client_name = me->base.session->client_name;
+			const mDNSBool uses_awdl = ClientRequestUsesAWDL(me->ifindex, me->flags);
+			mdns_powerlog_getaddrinfo_stop(client_name, me->powerlog_start_time, uses_awdl);
+			me->powerlog_start_time = 0;
+		}
+	#endif
 		_dx_query_record_client_request_forget(&me->query);
 	});
 }
@@ -2176,6 +2227,9 @@
 				}
 				SetOrClearBits(&me->state, dx_gai_state_avoid_suppressed_a_result, false);
 				break;
+
+			default:
+				break;
 		}
 	}
 	_dx_gai_request_append_result(me, result);
@@ -2330,8 +2384,10 @@
 
 		case (kDNSServiceProtocol_IPv4 | kDNSServiceProtocol_IPv6):
 			return true;
+
+		default:
+			return false;
 	}
-	return false;
 }
 
 //======================================================================================================================
@@ -2420,6 +2476,9 @@
 					case kDNSType_HTTPS:
 						me->state &= ~DX_GAI_STATE_WAITING_FOR_RESULTS;
 						break;
+
+					default:
+						break;
 				}
 			} else {
 				switch (answer->rrtype) {
@@ -2430,6 +2489,9 @@
 					case kDNSServiceType_AAAA:
 						me->state &= ~dx_gai_state_waiting_for_aaaa;
 						break;
+
+					default:
+						break;
 				}
 				const dx_gai_state_t state = me->state;
 				if (!(state & DX_GAI_STATE_WAITING_FOR_RESULTS) && (state & dx_gai_state_service_allowed_failover)) {
@@ -2632,7 +2694,6 @@
 static bool
 _dx_qc_result_is_add(const QC_result qc_result)
 {
-	// No default case to allow the compiler to catch missing enum values.
 	switch (qc_result) {
 		case QC_rmv:
 			return false;
@@ -2641,9 +2702,9 @@
 		case QC_addnocache:
 		case QC_forceresponse:
 		case QC_suppressed:
-			break;
+		MDNS_COVERED_SWITCH_DEFAULT:
+			return true;
 	}
-	return true;
 }
 
 //======================================================================================================================
@@ -2651,7 +2712,6 @@
 static bool
 _dx_qc_result_is_suppressed(const QC_result qc_result)
 {
-	// No default case to allow the compiler to catch missing enum values.
 	switch (qc_result) {
 		case QC_suppressed:
 			return true;
@@ -2660,9 +2720,9 @@
 		case QC_add:
 		case QC_addnocache:
 		case QC_forceresponse:
-			break;
+		MDNS_COVERED_SWITCH_DEFAULT:
+			return false;
 	}
-	return false;
 }
 
 //======================================================================================================================
diff --git a/mDNSMacOSX/dnssd_xpc.c b/mDNSMacOSX/dnssd_xpc.c
index 677fc2b..88e24ee 100644
--- a/mDNSMacOSX/dnssd_xpc.c
+++ b/mDNSMacOSX/dnssd_xpc.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2019-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -365,16 +365,18 @@
 {
 	bool valid;
 	// Make sure that dnssd_log_privacy_level_t is indeed an 8-bit signed integer.
-	check_compile_time_code(sizeof(dnssd_log_privacy_level_t) == sizeof(int8_t));
-	check_compile_time_code(((dnssd_log_privacy_level_t)-1) < 0);
+	mdns_compile_time_check_local(sizeof(dnssd_log_privacy_level_t) == sizeof(int8_t));
+	mdns_compile_time_check_local(((dnssd_log_privacy_level_t)-1) < 0);
 	const dnssd_log_privacy_level_t level = mdns_xpc_dictionary_get_int8(params,
 		DNSSD_XPC_PARAMETERS_KEY_LOG_PRIVACY_LEVEL, &valid);
 	if (valid) {
-		// A default case isn't used to allow the compiler to catch missing dnssd_log_privacy_level_t enum values.
 		switch (level) {
 			case dnssd_log_privacy_level_default:
 			case dnssd_log_privacy_level_private:
 				return level;
+
+			MDNS_COVERED_SWITCH_DEFAULT:
+				break;
 		}
 	}
 	return dnssd_log_privacy_level_default;
@@ -679,12 +681,11 @@
 {
 	bool valid;
 	// Make sure that dnssd_negative_reason_t is indeed a 32-bit signed integer.
-	check_compile_time_code(sizeof(dnssd_negative_reason_t) == sizeof(int32_t));
-	check_compile_time_code(((dnssd_negative_reason_t)-1) < 0);
+	mdns_compile_time_check_local(sizeof(dnssd_negative_reason_t) == sizeof(int32_t));
+	mdns_compile_time_check_local(((dnssd_negative_reason_t)-1) < 0);
 	const dnssd_negative_reason_t reason = mdns_xpc_dictionary_get_int32(result,
 		DNSSD_XPC_RESULT_KEY_NEGATIVE_REASON, &valid);
 	if (valid) {
-		// A default case isn't used to allow the compiler to catch missing dnssd_negative_reason_t enum values.
 		switch (reason) {
 			case dnssd_negative_reason_none:
 			case dnssd_negative_reason_no_data:
@@ -693,6 +694,9 @@
 			case dnssd_negative_reason_no_dns_service:
 			case dnssd_negative_reason_server_error:
 				return reason;
+
+			MDNS_COVERED_SWITCH_DEFAULT:
+				break;
 		}
 	}
 	return dnssd_negative_reason_none;
diff --git a/mDNSMacOSX/libmrc/mrc/cached_local_records_inquiry.h b/mDNSMacOSX/libmrc/mrc/cached_local_records_inquiry.h
new file mode 100644
index 0000000..a444f65
--- /dev/null
+++ b/mDNSMacOSX/libmrc/mrc/cached_local_records_inquiry.h
@@ -0,0 +1,202 @@
+/*
+ * Copyright (c) 2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MRC_CACHED_LOCAL_RECORDS_INQUIRY_H
+#define MRC_CACHED_LOCAL_RECORDS_INQUIRY_H
+
+#if !defined(MRC_ALLOW_HEADER_INCLUDES) || !MRC_ALLOW_HEADER_INCLUDES
+	#error "Please include <mrc/private.h> instead of this file directly."
+#endif
+
+#include <mrc/object.h>
+
+#include <dispatch/dispatch.h>
+#include <MacTypes.h>
+#include <mdns/base.h>
+#include <xpc/xpc.h>
+
+MRC_DECL(cached_local_records_inquiry);
+
+MDNS_ASSUME_NONNULL_BEGIN
+
+__BEGIN_DECLS
+
+/*!
+ *	@brief
+ *		Creates an inquiry to get basic information about local records from the system's mDNS record cache.
+ *
+ *	@result
+ *		A reference to the new inquiry, or NULL if creation failed due to a lack of system resources.
+ *
+ *	@discussion
+ *		The basic information consists of the following, which is extracted from the most recent snapshot of the
+ *		system's mDNS record cache:
+ *
+ *			1. The names of records from the local domain.
+ *			2. The names and RDATA of *._device-info._tcp.local TXT records.
+ *
+ *		Note that this is an extremely special purpose SPI that was driven by the needs of the Data Access team,
+ *		which included not generating any network traffic. In general, processes shouldn't depend on the current
+ *		content of the system's record cache, but instead use the normal DNS-SD API.
+ *
+ *		If not using Objective-C ARC, use mrc_retain() and mrc_release() to retain and release references to the
+ *		object.
+ */
+MDNS_SPI_AVAILABLE_FALL_2024
+MDNS_RETURNS_RETAINED MDNS_WARN_RESULT
+mrc_cached_local_records_inquiry_t _Nullable
+mrc_cached_local_records_inquiry_create(void);
+
+/*!
+ *	@brief
+ *		Sets the queue on which an inquiry is to invoke its result handler.
+ *
+ *	@param inquiry
+ *		The inquiry.
+ *
+ *	@param queue
+ *		The dispatch queue.
+ *
+ *	@discussion
+ *		A dispatch queue must be set in order for an inquiry's result handler to be invoked.
+ *
+ *		This function should be called before the inquiry is activated. This function has no effect on an
+ *		inquiry that has been activated or invalidated.
+ */
+MDNS_SPI_AVAILABLE_FALL_2024
+void
+mrc_cached_local_records_inquiry_set_queue(mrc_cached_local_records_inquiry_t inquiry, dispatch_queue_t queue);
+
+/*!
+ *	@brief
+ *		An inquiry result handler, which is used for passing the results of the inquiry.
+ *
+ *	@param record_info
+ *		An XPC array of XPC dictionaries, where each dictionary describes one cached local record.
+ *
+ *	@param error
+ *		An error code, which will be non-zero if a fatal error occurred, or zero, otherwise.
+ *
+ *	@discussion
+ *		The dictionary contains the following values:
+ *
+ *		1. The first label of the record name as a string. This value can be accessed with the
+ *		   mrc_cached_local_record_key_first_label key. The string doesn't contain any backslash escape
+ *		   sequences except for literal backslashes, non-printing ASCII characters (ASCII code points outside of
+ *		   the 0x20 - 0x7E range), and bytes that are not part of a valid UTF-8 byte sequence. Literal
+ *		   backslashes are represented as "\\", while each byte of the latter two cases is represented as the
+ *		   "\xHH" escape sequence, where HH is the hex value of the byte encoded as a pair of ASCII hex digits.
+ *		2. The record's name as a string. This value can be accessed with the mrc_cached_local_record_key_name
+ *		   key. This value is always present.
+ *		3. If a TXT record belonging to a _device-info._tcp.local subdomain was present in the cache, then the
+ *		   corresponding dictionary will contain the record's RDATA's text representation as a string. This
+ *		   value can be accessed with the mrc_cached_local_record_key_rdata key.
+ *		4. The source IPv4 or IPv6 address in text representation as a string. This value can be accessed with
+ *		   the mrc_cached_local_record_key_source_address key. This value is usually present, but not guaranteed
+ *		   to be present.
+ */
+typedef void
+(^mrc_cached_local_records_inquiry_result_handler_t)(xpc_object_t _Nullable record_info, OSStatus error);
+
+/*!
+ *	@brief
+ *		First label of the record name as a string with minimal backslash escape sequences.
+ */
+MDNS_SPI_AVAILABLE_FALL_2024
+extern const char * const mrc_cached_local_record_key_first_label;
+
+/*!
+ *	@brief
+ *		Record name as a string.
+ */
+MDNS_SPI_AVAILABLE_FALL_2024
+extern const char * const mrc_cached_local_record_key_name;
+
+/*!
+ *	@brief
+ *		Record RDATA's text representation as a string.
+ */
+MDNS_SPI_AVAILABLE_FALL_2024
+extern const char * const mrc_cached_local_record_key_rdata;
+
+/*!
+ *	@brief
+ *		Record's source IPv4 or IPv6 address's text representation as a string.
+ */
+MDNS_SPI_AVAILABLE_FALL_2024
+extern const char * const mrc_cached_local_record_key_source_address;
+
+/*!
+ *	@brief
+ *		Sets an inquiry's result handler.
+ *
+ *	@param inquiry
+ *		The inquiry.
+ *
+ *	@param handler
+ *		The result handler.
+ *
+ *	@discussion
+ *		The result handler will never be invoked before the first call to either
+ *		mrc_cached_local_records_inquiry_activate() or mrc_cached_local_records_inquiry_invalidate().
+ *
+ *		This function should be called before the inquiry is activated because it has no effect on an inquiry
+ *		that has been activated or invalidated.
+ */
+MDNS_SPI_AVAILABLE_FALL_2024
+void
+mrc_cached_local_records_inquiry_set_result_handler(mrc_cached_local_records_inquiry_t inquiry,
+	mrc_cached_local_records_inquiry_result_handler_t handler);
+
+/*!
+ *	@brief
+ *		Activates an inquiry.
+ *
+ *	@param inquiry
+ *		The inquiry.
+ *
+ *	@discussion
+ *		This function has no effect on an inquiry that has already been activated or invalidated.
+ */
+MDNS_SPI_AVAILABLE_FALL_2024
+void
+mrc_cached_local_records_inquiry_activate(mrc_cached_local_records_inquiry_t inquiry);
+
+/*!
+ *	@brief
+ *		Invalidates an inquiry.
+ *
+ *	@param inquiry
+ *		The inquiry.
+ *
+ *	@discussion
+ *		This function exists to gracefully invalidate an inquiry.
+ *
+ *		If the inquiry's result handler hasn't already been invoked and isn't just about to be invoked, then
+ *		this function will force it to be invoked (asynchronously, of course). If the result handler has
+ *		already been invoked, then calling this function is unnecessary, but harmless.
+ *
+ *		This function has no effect on an inquiry that has already been invalidated.
+ */
+MDNS_SPI_AVAILABLE_FALL_2024
+void
+mrc_cached_local_records_inquiry_invalidate(mrc_cached_local_records_inquiry_t inquiry);
+
+__END_DECLS
+
+MDNS_ASSUME_NONNULL_END
+
+#endif	// MRC_CACHED_LOCAL_RECORDS_INQUIRY_H
diff --git a/mDNSMacOSX/libmrc/mrc/discovery_proxy.h b/mDNSMacOSX/libmrc/mrc/discovery_proxy.h
new file mode 100644
index 0000000..697a7fa
--- /dev/null
+++ b/mDNSMacOSX/libmrc/mrc/discovery_proxy.h
@@ -0,0 +1,338 @@
+/*
+ * Copyright (c) 2023 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MRC_DISCOVERY_PROXY_H
+#define MRC_DISCOVERY_PROXY_H
+
+#if !defined(MRC_ALLOW_HEADER_INCLUDES) || !MRC_ALLOW_HEADER_INCLUDES
+	#error "Please include <mrc/private.h> instead of this file directly."
+#endif
+
+#include <mrc/object.h>
+
+#include <dispatch/dispatch.h>
+#include <MacTypes.h>
+#include <mdns/base.h>
+
+MRC_DECL(discovery_proxy);
+MRC_DECL(discovery_proxy_parameters);
+
+MDNS_ASSUME_NONNULL_BEGIN
+
+__BEGIN_DECLS
+
+/*!
+ *	@brief
+ *		Creates a discovery proxy based on the specified discovery proxy parameters.
+ *
+ *	@param params
+ *		The parameters.
+ *
+ *	@result
+ *		A reference to the new discovery proxy object, or NULL if creation failed.
+ *
+ *	@discussion
+ *		The parameters are copied by the discovery proxy object during its creation, so the discovery proxy will
+ *		not be affected by later changes to the parameters object.
+ *
+ *		If not using Objective-C ARC, use mrc_retain() and mrc_release() to retain and release references to the
+ *		object.
+ */
+MDNS_SPI_AVAILABLE_FALL_2024
+MDNS_RETURNS_RETAINED MDNS_WARN_RESULT
+mrc_discovery_proxy_t _Nullable
+mrc_discovery_proxy_create(mrc_discovery_proxy_parameters_t params);
+
+/*!
+ *	@brief
+ *		Sets a discovery proxy's dispatch queue on which to invoke the discovery proxy's event handler.
+ *
+ *	@param proxy
+ *		The discovery proxy.
+ *
+ *	@param queue
+ *		The dispatch queue.
+ *
+ *	@discussion
+ *		A dispatch queue must be set in order for the event handler to be invoked.
+ *
+ *		This function has no effect on a discovery proxy that has been activated or invalidated.
+ */
+MDNS_SPI_AVAILABLE_FALL_2024
+void
+mrc_discovery_proxy_set_queue(mrc_discovery_proxy_t proxy, dispatch_queue_t queue);
+
+/*!
+ *	@typedef mrc_discovery_proxy_event_t
+ *
+ *	@brief
+ *		The event type to be delivered when discovery proxy's event handler is invoked.
+ *
+ *	@const mrc_discovery_proxy_event_invalidation
+ *		The event that indicates that the discovery proxy has been invalidated.
+ *
+ *	@const mrc_discovery_proxy_event_none
+ *		The default event is a placeholder event value, it will never be delivered to the event handler.
+ *
+ *	@const mrc_discovery_proxy_event_started
+ *		The event that indicates that the discovery proxy has been started.
+ *
+ *	@const mrc_discovery_proxy_event_interruption
+ *		The event that indicates that the discovery proxy has been interrupted.
+ */
+MDNS_CLOSED_ENUM(mrc_discovery_proxy_event_t, int,
+	mrc_discovery_proxy_event_invalidation	= -1,
+	mrc_discovery_proxy_event_none			=  0,
+	mrc_discovery_proxy_event_started		=  1,
+	mrc_discovery_proxy_event_interruption	=  2
+);
+
+/*!
+ *	@brief
+ *		A discovery proxy event handler.
+ *
+ *	@param event
+ *		The event.
+ *
+ *	@param error
+ *		An error code associated with the event.
+ */
+typedef void
+(^mrc_discovery_proxy_event_handler_t)(mrc_discovery_proxy_event_t event, OSStatus error);
+
+/*!
+ *	@brief
+ *		Sets a discovery proxy's event handler.
+ *
+ *	@param proxy
+ *		The discovery proxy.
+ *
+ *	@param handler
+ *		The event handler.
+ *
+ *	@discussion
+ *		The event handler will never be invoked before the first call to either mrc_discovery_proxy_activate()
+ *		or mrc_discovery_proxy_invalidate().
+ *
+ *		The handler will be invoked with the mrc_discovery_proxy_event_invalidation event at most once if either
+ *		a fatal error occurs or if the the discovery proxy was manually invalidated with
+ *		mrc_discovery_proxy_invalidate(). If a fatal error occurred, the event handler's error code argument
+ *		will be non-zero to indicate the error that occurred. If the discovery proxy was gracefully invalidated
+ *		with mrc_discovery_proxy_invalidate(), then the event handler's error code will be kNoErr (0).
+ *
+ *		After the handler is invoked with the mrc_discovery_proxy_event_invalidation event, the handler will be
+ *		released by the discovery proxy, and will never be invoked by the discovery proxy ever again.
+ *
+ *		The handler will be invoked with the mrc_discovery_proxy_event_started event when a remote instance of
+ *		the discovery proxy has successfully started.
+ *
+ *		The handler will be invoked with the mrc_discovery_proxy_event_interruption event if the connection to
+ *		the daemon was interrupted. For example, the daemon may have crashed. This event exists to inform the
+ *		user that the remote instance of the discovery proxy may have suffered some downtime. The discovery
+ *		proxy object will try to re-establish the connection as well as a new remote instance of the discovery
+ *		proxy.
+ *
+ *		The mrc_discovery_proxy_event_none event simply exists as a placeholder event value. The event handler
+ *		will never be invoked with the mrc_discovery_proxy_event_none event.
+ *
+ *		Currently, the event handler's error code argument is only meaningful for the
+ *		mrc_discovery_proxy_event_invalidation event.
+ *
+ *		This function has no effect on a discovery proxy that has been activated or invalidated.
+ */
+MDNS_SPI_AVAILABLE_FALL_2024
+void
+mrc_discovery_proxy_set_event_handler(mrc_discovery_proxy_t proxy, mrc_discovery_proxy_event_handler_t handler);
+
+/*!
+ *	@brief
+ *		Activates a discovery proxy.
+ *
+ *	@param proxy
+ *		The discovery proxy.
+ *
+ *	@discussion
+ *		Activating a discovery proxy object causes it to try to instantiate a remote instance of itself on the
+ *		system daemon that implements discovery proxy.
+ *
+ *		Currently, the maximum number of discovery proxy that can be configured in the system at the same time
+ *		is 1. If the system has already been configured with one, the second call to activate a new proxy will
+ *		fail with error code `kAlreadyInitializedErr` to indicate that the discovery proxy has been started.
+ *		Only when the previous registration is invalidated or the client is interrupted, for example, the client
+ *		may have crashed, can the following registration activates a new discovery proxy.
+ *
+ *		This function has no effect on a discovery proxy that has already been activated or invalidated.
+ */
+MDNS_SPI_AVAILABLE_FALL_2024
+void
+mrc_discovery_proxy_activate(mrc_discovery_proxy_t proxy);
+
+/*!
+ *	@brief
+ *		Invalidates a discovery proxy.
+ *
+ *	@param proxy
+ *		The discovery proxy.
+ *
+ *	@discussion
+ *		Invalidating a discovery proxy object causes it to tear down the remote instance of itself on the system
+ *		daemon if such an instance exists.
+ *
+ *		This function exists to gracefully invalidate a discovery proxy. This function can safely be called even
+ *		if the discovery proxy was already forcibly invalidated due to a fatal error.
+ *
+ *		This function has no effect on a discovery proxy that has already been invalidated.
+ */
+MDNS_SPI_AVAILABLE_FALL_2024
+void
+mrc_discovery_proxy_invalidate(mrc_discovery_proxy_t proxy);
+
+/*!
+ *	@brief
+ *		Creates an empty modifiable set of discovery proxy parameters.
+ *
+ *	@result
+ *		A reference to the new discovery proxy parameters, or NULL if creation failed.
+ *
+ *	@discussion
+ *		If not using Objective-C ARC, use mrc_retain() and mrc_release() to retain and release references to the
+ *		object.
+ */
+MDNS_SPI_AVAILABLE_FALL_2024
+MDNS_RETURNS_RETAINED MDNS_WARN_RESULT
+mrc_discovery_proxy_parameters_t _Nullable
+mrc_discovery_proxy_parameters_create(void);
+
+/*!
+ *	@brief
+ *		Sets the interface index in a set of discovery proxy parameters.
+ *
+ *	@param params
+ *		The parameters.
+ *
+ *	@param ifindex
+ *		The interface index.
+ */
+MDNS_SPI_AVAILABLE_FALL_2024
+void
+mrc_discovery_proxy_parameters_set_interface(mrc_discovery_proxy_parameters_t params, uint32_t ifindex);
+
+/*!
+ *	@brief
+ *		Adds an IPv4 address to an array of discovery proxy parameters.
+ *
+ *	@param params
+ *		The parameters.
+ *
+ *	@param address
+ *		The IPv4 address as an unsigned 32-bit integer in host byte order.
+ *
+ *	@param port
+ *		The port number in host byte order.
+ *
+ *	@result
+ *		kNoErr if the IPv4 address were successfully added. Otherwise, a non-zero error code to indicate why the
+ *		operation failed.
+ *
+ *	@discussion
+ *		This function can be called more than once to specify an array of multiple IPv4 addresses.
+ */
+MDNS_SPI_AVAILABLE_FALL_2024
+OSStatus
+mrc_discovery_proxy_parameters_add_server_ipv4_address(mrc_discovery_proxy_parameters_t params, uint32_t address,
+	uint16_t port);
+
+/*!
+ *	@brief
+ *		Adds an IPv6 address to an array of discovery proxy parameters.
+ *
+ *	@param params
+ *		The parameters.
+ *
+ *	@param address
+ *		The IPv6 address as an array of octets in network byte order.
+ *
+ *	@param port
+ *		The port number in host byte order.
+ *
+ *	@param scope_id
+ *		The scope ID.
+ *
+ *	@result
+ *		kNoErr if the IPv6 address were successfully added. Otherwise, a non-zero error code to indicate why the
+ *		operation failed.
+ *
+ *	@discussion
+ *		This function can be called more than once to specify an array of multiple IPv6 addresses.
+ */
+MDNS_SPI_AVAILABLE_FALL_2024
+OSStatus
+mrc_discovery_proxy_parameters_add_server_ipv6_address(mrc_discovery_proxy_parameters_t params,
+	const uint8_t address[static 16], uint16_t port, uint32_t scope_id);
+
+/*!
+ *	@brief
+ *		Adds a matching domain to a set of discovery proxy parameters.
+ *
+ *	@param params
+ *		The parameters.
+ *
+ *	@param domain
+ *		The domain name string.
+ *
+ *	@result
+ *		kNoErr if the domain were successfully added. Otherwise, a non-zero error code to indicate why the
+ *		operation failed.
+ *
+ *	@discussion
+ *		This function can be called more than once to specify a set of multiple matching domains.
+ *
+ *		When discovery proxy is configured, queries under the specified domains will always use the proxy as the
+ *		preferred service.
+ */
+MDNS_SPI_AVAILABLE_FALL_2024
+OSStatus
+mrc_discovery_proxy_parameters_add_match_domain(mrc_discovery_proxy_parameters_t params, const char *domain);
+
+/*!
+ *	@brief
+ *		Adds a TLS certificate to an array of discovery proxy parameters.
+ *
+ *	@param params
+ *		The parameters.
+ *
+ *	@param cert_data
+ *		The data of the DER representation of an X.509 certificate.
+ *
+ *	@param cert_len
+ *		The data length of the DER representation of an X.509 certificate.
+ *
+ *	@discussion
+ *		This function can be called more than once to specify an array of multiple certificates.
+ *
+ *		When the client intends to use TLS to communicate with the discovery proxy, the certificates specified
+ *		here can be used as additional trust anchors to perform TLS certificate evaluation.
+ */
+MDNS_SPI_AVAILABLE_FALL_2024
+void
+mrc_discovery_proxy_parameters_add_server_certificate(mrc_discovery_proxy_parameters_t params, const uint8_t *cert_data,
+	size_t cert_len);
+
+__END_DECLS
+
+MDNS_ASSUME_NONNULL_END
+
+#endif	// MRC_DISCOVERY_PROXY_H
diff --git a/mDNSMacOSX/libmrc/mrc/dns_proxy.h b/mDNSMacOSX/libmrc/mrc/dns_proxy.h
index c493f1a..dc47450 100644
--- a/mDNSMacOSX/libmrc/mrc/dns_proxy.h
+++ b/mDNSMacOSX/libmrc/mrc/dns_proxy.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021-2022 Apple Inc. All rights reserved.
+ * Copyright (c) 2021-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -95,8 +95,8 @@
 		case mrc_dns_proxy_event_none:			return "none";
 		case mrc_dns_proxy_event_started:		return "started";
 		case mrc_dns_proxy_event_interruption:	return "interruption";
+		MDNS_COVERED_SWITCH_DEFAULT:			return "<INVALID EVENT>";
 	}
-	return "<INVALID EVENT>";
 }
 
 /*!
diff --git a/mDNSMacOSX/libmrc/mrc/dns_service_registration.h b/mDNSMacOSX/libmrc/mrc/dns_service_registration.h
new file mode 100644
index 0000000..d01e5db
--- /dev/null
+++ b/mDNSMacOSX/libmrc/mrc/dns_service_registration.h
@@ -0,0 +1,257 @@
+/*
+ * Copyright (c) 2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MRC_DNS_SERVICE_REGISTRATION_H
+#define MRC_DNS_SERVICE_REGISTRATION_H
+
+#if !defined(MRC_ALLOW_HEADER_INCLUDES) || !MRC_ALLOW_HEADER_INCLUDES
+	#error "Please include <mrc/private.h> instead of this file directly."
+#endif
+
+#include <mrc/object.h>
+
+#include <dispatch/dispatch.h>
+#include <MacTypes.h>
+#include <mdns/base.h>
+#include <mdns/dns_service.h>
+
+MRC_DECL(dns_service_registration);
+
+MDNS_ASSUME_NONNULL_BEGIN
+
+__BEGIN_DECLS
+
+/*!
+ *	@brief
+ *		Creates a DNS service registration based on the specified DNS service definition.
+ *
+ *	@param definition
+ *		The DNS service definition.
+ *
+ *	@result
+ *		A reference to the new DNS service registration object, or NULL if creation failed.
+ *
+ *	@discussion
+ *		The DNS service definition is copied by the DNS service registration object during its creation, so the
+ *		registration will not be affected by later changes to the definition.
+ *
+ *		If not using Objective-C ARC, use mrc_retain() and mrc_release() to retain and release references to the
+ *		object.
+ */
+MDNS_SPI_AVAILABLE_SPRING_2024
+MDNS_RETURNS_RETAINED MDNS_WARN_RESULT
+mrc_dns_service_registration_t _Nullable
+mrc_dns_service_registration_create(mdns_dns_service_definition_t definition);
+
+/*!
+ *	@brief
+ *		Creates a DNS push service registration based on the specified DNS push service definition.
+ *
+ *	@param definition
+ *		The DNS push service definition.
+ *
+ *	@result
+ *		A reference to the new DNS push service registration object, or NULL if creation failed.
+ *
+ *	@discussion
+ *		The DNS push service definition is copied by the DNS service registration object during its creation, so
+ *		the registration will not be affected by later changes to the definition.
+ *
+ *		If not using Objective-C ARC, use mrc_retain() and mrc_release() to retain and release references to the
+ *		object.
+ */
+MDNS_PROJECT_ONLY_SPI_AVAILABLE_FALL_2024
+MDNS_RETURNS_RETAINED MDNS_WARN_RESULT
+mrc_dns_service_registration_t _Nullable
+mrc_dns_service_registration_create_push(mdns_dns_push_service_definition_t definition);
+
+/*!
+ *	@brief
+ *		Specifies whether the registration should report the connection error, if the service being registered is a DNS push service.
+ *
+ *	@param definition
+ *		The DNS push service definition.
+ *
+ *	@param reports_connection_errors
+ *		Whether a connection error should be reported.
+ */
+MDNS_PROJECT_ONLY_SPI_AVAILABLE_FALL_2024
+void
+mrc_dns_service_registration_set_reports_connection_errors(mrc_dns_service_registration_t definition,
+	bool reports_connection_errors);
+
+/*!
+ *	@brief
+ *		Sets a DNS service registration's dispatch queue on which to invoke the DNS service registration's event
+ *		handler.
+ *
+ *	@param registration
+ *		The DNS service registration.
+ *
+ *	@param queue
+ *		The dispatch queue.
+ *
+ *	@discussion
+ *		A dispatch queue must be set in order for the event handler to be invoked.
+ *
+ *		This function has no effect on a DNS service registration that has already been activated or
+ *		invalidated.
+ */
+MDNS_SPI_AVAILABLE_SPRING_2024
+void
+mrc_dns_service_registration_set_queue(mrc_dns_service_registration_t registration, dispatch_queue_t queue);
+
+/*!
+ *	@typedef mrc_dns_service_registration_event_t
+ *
+ *	@brief
+ *		DNS service registration events.
+ *
+ *	@const mrc_dns_service_registration_event_invalidation
+ *		Indicates that the DNS service registration has been invalidated.
+ *
+ *	@const mrc_dns_service_registration_event_started
+ *		Indicates that the DNS service registration has started.
+ *
+ *	@const mrc_dns_service_registration_event_interruption
+ *		Indicates that the DNS service registration has been interrupted.
+ *
+ *	@const mrc_dns_service_registration_event_connection_error
+ *		Indicates that the DNS service registration has encountered a connection error.
+ */
+MDNS_CLOSED_ENUM(mrc_dns_service_registration_event_t, int,
+	mrc_dns_service_registration_event_invalidation		= -1,
+	mrc_dns_service_registration_event_started			=  1,
+	mrc_dns_service_registration_event_interruption		=  2,
+	mrc_dns_service_registration_event_connection_error	=  3,
+);
+
+/*!
+ *	@brief
+ *		A DNS service registration event handler.
+ *
+ *	@param event
+ *		The event.
+ *
+ *	@param error
+ *		An error code associated with the event.
+ */
+typedef void
+(^mrc_dns_service_registration_event_handler_t)(mrc_dns_service_registration_event_t event, OSStatus error);
+
+/*!
+ *	@brief
+ *		Sets a DNS service registration's event handler.
+ *
+ *	@param registration
+ *		The DNS service registration.
+ *
+ *	@param handler
+ *		The event handler.
+ *
+ *	@discussion
+ *		The event handler will never be invoked before the first call to either
+ *		mrc_dns_service_registration_activate() or mrc_dns_service_registration_invalidate().
+ *
+ *		The handler will be invoked with the mrc_dns_service_registration_event_invalidation event at most once
+ *		if either a fatal error occurs or if the the DNS service registration was manually invalidated with
+ *		mrc_dns_service_registration_invalidate(). If a fatal error occurred, the event handler's error code
+ *		argument will be non-zero to indicate the error that occurred. If the DNS service registration was
+ *		gracefully invalidated with mrc_dns_service_registration_invalidate(), then the event handler's error
+ *		code will be kNoErr (0).
+ *
+ *		After the handler is invoked with the mrc_dns_service_registration_event_invalidation event, the handler
+ *		will be released by the DNS service registration, and will never be invoked by the DNS service
+ *		registration ever again.
+ *
+ *		The handler will be invoked with the mrc_dns_service_registration_event_started event each time a remote
+ *		instance of the DNS service registration has successfully started.
+ *
+ *		The handler will be invoked with the mrc_dns_service_registration_event_interruption event if the
+ *		connection to the daemon was interrupted. For example, the daemon may have crashed. This event exists to
+ *		inform the user that the remote instance of the DNS service registration may have suffered some
+ *		downtime. The DNS service registration object will try to re-establish the connection as well as a new
+ *		remote instance of the DNS service registration.
+ *
+ *		Currently, the event handler's error code argument is only meaningful for the
+ *		mrc_dns_service_registration_event_invalidation event.
+ *
+ *		This function has no effect on a DNS service registration that has already been activated or
+ *		invalidated.
+ */
+MDNS_SPI_AVAILABLE_SPRING_2024
+void
+mrc_dns_service_registration_set_event_handler(mrc_dns_service_registration_t registration,
+	mrc_dns_service_registration_event_handler_t handler);
+
+/*!
+ *	@brief
+ *		Activates a DNS service registration.
+ *
+ *	@param registration
+ *		The DNS service registration.
+ *
+ *	@discussion
+ *		Activating a DNS service registration object causes it to try to instantiate a remote instance of itself
+ *		on the system daemon that implements DNS service registrations.
+ *
+ *		This function has no effect on a DNS service registration that has already been activated or
+ *		invalidated.
+ */
+MDNS_SPI_AVAILABLE_SPRING_2024
+void
+mrc_dns_service_registration_activate(mrc_dns_service_registration_t registration);
+
+/*!
+ *	@brief
+ *		Invalidates a DNS service registration.
+ *
+ *	@param registration
+ *		The DNS service registration.
+ *
+ *	@discussion
+ *		Invalidating a DNS service registration object causes it to tear down the remote instance of itself on
+ *		the system daemon if such an instance exists.
+ *
+ *		This function exists to gracefully invalidate a DNS service registration. This function can safely be
+ *		called even if the DNS service registration was already forcibly invalidated due to a fatal error.
+ *
+ *		This function has no effect on a DNS service registration that has already been invalidated.
+ */
+MDNS_SPI_AVAILABLE_SPRING_2024
+void
+mrc_dns_service_registration_invalidate(mrc_dns_service_registration_t registration);
+
+/*!
+ *	@brief
+ *		Invalidates and forgets a DNS service registration.
+ *
+ *	@param PTR
+ *		The address of a DNS service registration pointer.
+ *
+ *	@discussion
+ *		This is a convenience macro that combines the functionality of mrc_dns_service_registration_invalidate()
+ *		and mrc_forget(). If the pointer is non-NULL, then the DNS service registration referenced by the pointer
+ *		is invalidated with mrc_dns_service_registration_invalidate(). The address of the DNS service registration
+ *		pointer is then treated as if it were passed to mrc_forget().
+ */
+#define mrc_dns_service_registration_forget(PTR)	mrc_forget_with_invalidation(PTR, dns_service_registration)
+
+__END_DECLS
+
+MDNS_ASSUME_NONNULL_END
+
+#endif	// MRC_DNS_SERVICE_REGISTRATION_H
diff --git a/mDNSMacOSX/libmrc/mrc/object.h b/mDNSMacOSX/libmrc/mrc/object.h
index 0e4b524..acd458b 100644
--- a/mDNSMacOSX/libmrc/mrc/object.h
+++ b/mDNSMacOSX/libmrc/mrc/object.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 Apple Inc. All rights reserved.
+ * Copyright (c) 2021-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -137,4 +137,13 @@
 		}									\
 	} while(0)
 
+#define mrc_forget_with_invalidation(PTR, NAME)		\
+	do {											\
+		if (*(PTR)) {								\
+			mrc_ ## NAME ## _invalidate(*(PTR));	\
+			mrc_release_arc_safe(*(PTR));			\
+			*(PTR) = NULL;							\
+		}											\
+	} while (0)
+
 #endif	// MRC_OBJECT_H
diff --git a/mDNSMacOSX/libmrc/mrc/object_members.h b/mDNSMacOSX/libmrc/mrc/object_members.h
index bf2073f..751e572 100644
--- a/mDNSMacOSX/libmrc/mrc/object_members.h
+++ b/mDNSMacOSX/libmrc/mrc/object_members.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021-2022 Apple Inc. All rights reserved.
+ * Copyright (c) 2021-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -23,10 +23,26 @@
 
 #define MRC_UNION_MEMBER(NAME)	struct mrc_ ## NAME ## _s *	_mrc_ ## NAME
 
-#define MRC_OBJECT_MEMBERS					\
-	MRC_UNION_MEMBER(object);				\
-	MRC_UNION_MEMBER(dns_proxy);			\
-	MRC_UNION_MEMBER(dns_proxy_parameters);	\
-	MRC_UNION_MEMBER(dns_proxy_state_inquiry);
+#define MRC_OBJECT_MEMBERS_EXTERNAL					\
+	MRC_UNION_MEMBER(object);						\
+	MRC_UNION_MEMBER(cached_local_records_inquiry);	\
+	MRC_UNION_MEMBER(discovery_proxy);				\
+	MRC_UNION_MEMBER(discovery_proxy_parameters);	\
+	MRC_UNION_MEMBER(dns_proxy);					\
+	MRC_UNION_MEMBER(dns_proxy_parameters);			\
+	MRC_UNION_MEMBER(dns_proxy_state_inquiry);		\
+	MRC_UNION_MEMBER(dns_service_registration);		\
+	MRC_UNION_MEMBER(record_cache_flush);			\
+	MRC_UNION_MEMBER(session);						\
+
+#if defined(MDNS_LIBMRC_BUILD) && MDNS_LIBMRC_BUILD
+	#include "mrc_object_members_internal.h"
+#else
+	#define MRC_OBJECT_MEMBERS_INTERNAL
+#endif
+
+#define MRC_OBJECT_MEMBERS		\
+	MRC_OBJECT_MEMBERS_EXTERNAL	\
+	MRC_OBJECT_MEMBERS_INTERNAL
 
 #endif	// MRC_OBJECT_MEMBERS_H
diff --git a/mDNSMacOSX/libmrc/mrc/private.h b/mDNSMacOSX/libmrc/mrc/private.h
index c4e01f9..ed2b019 100644
--- a/mDNSMacOSX/libmrc/mrc/private.h
+++ b/mDNSMacOSX/libmrc/mrc/private.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 Apple Inc. All rights reserved.
+ * Copyright (c) 2021-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -20,7 +20,11 @@
 #undef MRC_ALLOW_HEADER_INCLUDES
 #define MRC_ALLOW_HEADER_INCLUDES 1
 
+#include <mrc/cached_local_records_inquiry.h>
+#include <mrc/discovery_proxy.h>
 #include <mrc/dns_proxy.h>
+#include <mrc/dns_service_registration.h>
+#include <mrc/record_cache_flush.h>
 
 #undef MRC_ALLOW_HEADER_INCLUDES
 
diff --git a/mDNSMacOSX/libmrc/mrc/record_cache_flush.h b/mDNSMacOSX/libmrc/mrc/record_cache_flush.h
new file mode 100644
index 0000000..42eeb46
--- /dev/null
+++ b/mDNSMacOSX/libmrc/mrc/record_cache_flush.h
@@ -0,0 +1,239 @@
+/*
+ * Copyright (c) 2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MRC_RECORD_CACHE_FLUSH_H
+#define MRC_RECORD_CACHE_FLUSH_H
+
+#if !defined(MRC_ALLOW_HEADER_INCLUDES) || !MRC_ALLOW_HEADER_INCLUDES
+	#error "Please include <mrc/private.h> instead of this file directly."
+#endif
+
+#include <mrc/object.h>
+
+#include <dispatch/dispatch.h>
+#include <MacTypes.h>
+#include <mdns/base.h>
+#include <xpc/xpc.h>
+
+MRC_DECL(record_cache_flush);
+
+MDNS_ASSUME_NONNULL_BEGIN
+
+__BEGIN_DECLS
+
+/*!
+ *	@brief
+ *		Creates an object that represents a record cache flush operation.
+ *
+ *	@result
+ *		A reference to the new object, or NULL if creation failed due to a lack of system resources.
+ *
+ *	@discussion
+ *		The conditions that must be met by a record in order to be flushed by the record cache flush can be
+ *		specified by functions such as mrc_record_cache_flush_set_record_name() and
+ *		mrc_record_cache_flush_set_key_tag().
+ *
+ *		If not using Objective-C ARC, use mrc_retain() and mrc_release() to retain and release references to the
+ *		object.
+ */
+MDNS_SPI_AVAILABLE_FALL_2024
+MDNS_RETURNS_RETAINED MDNS_WARN_RESULT
+mrc_record_cache_flush_t _Nullable
+mrc_record_cache_flush_create(void);
+
+/*!
+ *	@brief
+ *		Sets the dispatch queue on which a record cache flush is to invoke its result handler.
+ *
+ *	@param flush
+ *		The record cache flush.
+ *
+ *	@param queue
+ *		The dispatch queue.
+ *
+ *	@discussion
+ *		A dispatch queue must be set in order for a record cache flush's result handler to be invoked.
+ *
+ *		This function should be called before the record cache flush is activated. This function has no effect
+ *		on a record cache flush that has been activated or invalidated.
+ */
+MDNS_SPI_AVAILABLE_FALL_2024
+void
+mrc_record_cache_flush_set_queue(mrc_record_cache_flush_t flush, dispatch_queue_t queue);
+
+/*!
+ *	@brief
+ *		Sets the name of the records to be flushed by a record cache flush.
+ *
+ *	@param flush
+ *		The record cache flush.
+ *
+ *	@param record_name
+ *		The record name.
+ *
+ *	@discussion
+ *		A record matching the specified record name is a necessary condition for it to be flushed by the record
+ *		cache flush.
+ *
+ *		This function should be called before the record cache flush is activated. This function has no effect
+ *		on a record cache flush that has been activated or invalidated.
+ */
+MDNS_SPI_AVAILABLE_FALL_2024
+void
+mrc_record_cache_flush_set_record_name(mrc_record_cache_flush_t flush, mdns_domain_name_t record_name);
+
+/*!
+ *	@brief
+ *		Sets the key tag that a record must match in order to be flushed by a record cache flush.
+ *
+ *	@param flush
+ *		The record cache flush.
+ *
+ *	@param key_tag
+ *		The key tag.
+ *
+ *	@discussion
+ *		A record matching the specified key tag is a necessary condition for it to be flushed by the record
+ *		cache flush.
+ *
+ *		This function has no effect on a record cache flush that has been activated or invalidated.
+ */
+MDNS_SPI_AVAILABLE_FALL_2024
+void
+mrc_record_cache_flush_set_key_tag(mrc_record_cache_flush_t flush, uint16_t key_tag);
+
+/*!
+ *	@brief
+ *		Indicates how a record cache flush concluded.
+ */
+typedef enum {
+	/*! @brief Indicates that a record cache flush was successfully carried out. */
+	mrc_record_cache_flush_result_complete		= 1,
+	/*! @brief Indicates that a record cache flush was not successfully carried out. */
+	mrc_record_cache_flush_result_incomplete	= 2,
+} mrc_record_cache_flush_result_t;
+
+/*!
+ *	@brief
+ *		A block that handles the result of a record cache flush.
+ *
+ *	@param result
+ *		The result of the record cache flush.
+ *
+ *	@param error
+ *		An error code, which is only relevant if the result is mrc_record_cache_flush_result_incomplete.
+ */
+typedef void
+(^mrc_record_cache_flush_result_handler_t)(mrc_record_cache_flush_result_t result, OSStatus error);
+
+/*!
+ *	@brief
+ *		Sets a record cache flush's result handler.
+ *
+ *	@param flush
+ *		The record cache flush.
+ *
+ *	@param handler
+ *		The result handler.
+ *
+ *	@discussion
+ *		The result handler will never be invoked before the first call to either
+ *		mrc_record_cache_flush_activate() or mrc_record_cache_flush_invalidate().
+ *
+ *		The result handler will be submitted to the record cache flush's dispatch queue no more than once as a
+ *		consequence of one of the following events, whichever occurs first:
+ *
+ *		1. The system daemon that manages the system's DNS record cache flushes all of the records that meet the
+ *		   criteria specified by the record cache flush. If no records in the record cache meet the criteria
+ *		   specified by the record cache flush, then there are no records to flush. In either case, the handler
+ *		   will be invoked with mrc_record_cache_flush_result_success and a kNoErr error code.
+ *
+ *		2. A fatal error occurs that prevents the record cache flush from successfully completing. In this case,
+ *		   the handler will be invoked with mrc_record_cache_flush_result_incomplete and a non-zero error code
+ *		   that indicates the type of error that occurred.
+ *
+ *		3. The record cache flush is invalidated with mrc_record_cache_flush_invalidate(). In this case, the
+ *		   handler will be invoked with mrc_record_cache_flush_result_incomplete and a kNoErr error code.
+ *
+ *		After the handler is invoked, the record cache flush's reference to the handler will be released.
+ *
+ *		This function should be called before the record cache flush is activated. This function has no effect
+ *		on a record cache flush that has been activated or invalidated.
+ */
+MDNS_SPI_AVAILABLE_FALL_2024
+void
+mrc_record_cache_flush_set_result_handler(mrc_record_cache_flush_t flush,
+	mrc_record_cache_flush_result_handler_t handler);
+
+/*!
+ *	@brief
+ *		Activates a record cache flush.
+ *
+ *	@param flush
+ *		The record cache flush.
+ *
+ *	@discussion
+ *		Calling this function initiates the request to the system daemon that manages the system's DNS record
+ *		cache to carry out the record cache flush.
+ *
+ *		This function has no effect on a record cache flush that has been already activated or invalidated.
+ */
+MDNS_SPI_AVAILABLE_FALL_2024
+void
+mrc_record_cache_flush_activate(mrc_record_cache_flush_t flush);
+
+/*!
+ *	@brief
+ *		Invalidates a record cache flush.
+ *
+ *	@param flush
+ *		The record cache flush.
+ *
+ *	@discussion
+ *		This function exists to gracefully invalidate a record cache flush that's no longer needed.
+ *
+ *		If the record cache flush's result handler hasn't already been invoked or its invocation isn't imminent,
+ *		then this function will force it to be invoked (asynchronously and on the record cache flush's dispatch
+ *		queue, of course). If the result handler has already been invoked, then calling this function is
+ *		unnecessary, but harmless.
+ *
+ *		This function has no effect on a record cache flush that has already been invalidated.
+ */
+MDNS_SPI_AVAILABLE_FALL_2024
+void
+mrc_record_cache_flush_invalidate(mrc_record_cache_flush_t flush);
+
+/*!
+ *	@brief
+ *		Invalidates and releases a record cache flush object referenced by a pointer.
+ *
+ *	@param OBJ_PTR
+ *		The address of the pointer that either references a record cache flush object or references NULL.
+ *
+ *	@discussion
+ *		This is a convenience macro that combines the functionality of mrc_record_cache_flush_invalidate() and
+ *		mrc_forget(). If the pointer contains a non-NULL reference, then the record cache flush object
+ *		referenced by the pointer is invalidated with mrc_record_cache_flush_invalidate(), then released. Also,
+ *		if the pointer contains a non-NULL reference, then the pointer will be set to NULL after releasing the
+ *		record cache flush object.
+ */
+#define mrc_record_cache_flush_forget(OBJ_PTR)	mrc_forget_with_invalidation(OBJ_PTR, record_cache_flush)
+
+__END_DECLS
+
+MDNS_ASSUME_NONNULL_END
+
+#endif	// MRC_RECORD_CACHE_FLUSH_H
diff --git a/mDNSMacOSX/libmrc/src/mrc.c b/mDNSMacOSX/libmrc/src/mrc.c
index ed49d6d..87cfb10 100644
--- a/mDNSMacOSX/libmrc/src/mrc.c
+++ b/mDNSMacOSX/libmrc/src/mrc.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2021-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,42 +14,190 @@
  * limitations under the License.
  */
 
-#include <mrc/dns_proxy.h>
+#include <mrc/private.h>
 
+#include "cf_support.h"
 #include "helpers.h"
 #include "memory.h"
+#include "mrc_cached_local_record_keys.h"
+#include "mrc_internal.h"
+#include "mrc_object_internal.h"
 #include "mrc_objects.h"
 #include "mrc_xpc.h"
+#include "us_ascii.h"
+#include "utf8.h"
 
 #include <arpa/inet.h>
 #include <CoreUtils/CoreUtils.h>
+#include <mdns/dns_service.h>
+#include <mdns/DNSMessage.h>
+#include <mdns/string_builder.h>
 #include <mdns/xpc.h>
 #include <os/log.h>
 #include "mdns_strict.h"
 
 //======================================================================================================================
-// MARK: - DNS Proxy Kind Definition
+// MARK: - Session Kind Definition
 
-OS_CLOSED_ENUM(mrc_dns_proxy_state, int8_t,
-	mrc_dns_proxy_state_failed		= -2,
-	mrc_dns_proxy_state_invalidated	= -1,
-	mrc_dns_proxy_state_nascent		=  0,
-	mrc_dns_proxy_state_starting	=  1,
-	mrc_dns_proxy_state_started		=  2
+MDNS_CLOSED_ENUM(mrc_session_event_t, uint8_t,
+	mrc_session_event_started		= 1,
+	mrc_session_event_interruption	= 2,
+	mrc_session_event_invalidated	= 3,
 );
 
-struct mrc_dns_proxy_s {
-	struct mdns_obj_s				base;			// Object base.
-	mrc_dns_proxy_t					next;			// Next DNS proxy object in list.
-	dispatch_queue_t				queue;			// User's dispatch queue.
-	xpc_object_t					params;			// DNS proxy parameters.
-	mrc_dns_proxy_event_handler_t	event_handler;	// Event handler.
-	uint64_t						cmd_id;			// Command ID.
-	mrc_dns_proxy_state_t			state;			// Current state.
-	bool							immutable;		// True if the DNS proxy is no longer externally mutable.
+MDNS_CLOSED_ENUM(mrc_session_state_t, int8_t,
+	mrc_session_state_invalidated	= -1,
+	mrc_session_state_nascent		=  0,
+	mrc_session_state_starting		=  1,
+	mrc_session_state_started		=  2,
+	mrc_session_state_done			=  3,
+);
+
+struct mrc_session_s {
+	struct mdns_obj_s	base;			// Object base.
+	uint64_t			cmd_id;			// Current command ID.
+	mrc_session_t		next;			// Next session in list.
+	mrc_client_t		client;			// Delegate object to use when invoking callbacks.
+	const char *		entity_name;	// For logging purposes, the name of the entity that is to be enabled.
+	mrc_session_state_t	state;			// Current state of the session.
 };
 
-MRC_OBJECT_SUBKIND_DEFINE(dns_proxy);
+MRC_OBJECT_SUBKIND_DEFINE(session);
+
+//======================================================================================================================
+// MARK: - Client Kind Definition
+
+MDNS_CLOSED_ENUM(mrc_client_state_t, uint8_t,
+	mrc_client_state_nascent		= 0,
+	mrc_client_state_activated		= 1,
+	mrc_client_state_invalidated	= 2,
+);
+
+struct mrc_client_s {
+	struct mdns_obj_s	base;		// Object base.
+	mrc_session_t		session;	// Used to enable the DNS proxy on mDNSResponder.
+	dispatch_queue_t	user_queue;	// User's dispatch queue.
+	mrc_client_state_t	state;		// Current state.
+	bool				immutable;	// True if the DNS proxy is no longer externally mutable.
+};
+
+MRC_OBJECT_SUBKIND_DEFINE_ABSTRACT_MINIMAL_WITHOUT_ALLOC(client);
+
+typedef union {
+	MRC_UNION_MEMBER(client);
+	MRC_UNION_MEMBER(cached_local_records_inquiry);
+	MRC_UNION_MEMBER(discovery_proxy);
+	MRC_UNION_MEMBER(dns_proxy);
+	MRC_UNION_MEMBER(dns_service_registration);
+	MRC_UNION_MEMBER(record_cache_flush);
+} mrc_any_client_t __attribute__((__transparent_union__));
+
+typedef xpc_object_t
+(*mrc_client_create_start_message_f)(mrc_any_client_t any, uint64_t cmd_id);
+
+typedef xpc_object_t
+(*mrc_client_create_stop_message_f)(mrc_any_client_t any, uint64_t cmd_id);
+
+typedef void
+(*mrc_client_handle_start_f)(mrc_any_client_t any, xpc_object_t result);
+
+typedef void
+(*mrc_client_handle_interruption_f)(mrc_any_client_t any);
+
+typedef void
+(*mrc_client_handle_invalidation_f)(mrc_any_client_t any, OSStatus error);
+
+typedef void
+(*mrc_client_handle_notification_f)(mrc_any_client_t any, xpc_object_t notification);
+
+typedef const struct mrc_client_kind_s *mrc_client_kind_t;
+struct mrc_client_kind_s {
+	struct mdns_kind_s					base;
+	mrc_client_create_start_message_f	create_start_message;
+	mrc_client_create_stop_message_f	create_stop_message;
+	mrc_client_handle_start_f			handle_start;
+	mrc_client_handle_interruption_f	handle_interruption;
+	mrc_client_handle_invalidation_f	handle_invalidation;
+	mrc_client_handle_notification_f	handle_notification;
+	const char *						operation_name;
+	bool								oneshot;
+};
+
+#define MRC_CLIENT_SUBKIND_DEFINE_CORE(NAME, OPERATION_NAME, ...)								\
+	static char *																				\
+	_mrc_ ## NAME ## _copy_description(mrc_ ## NAME ## _t client, bool debug, bool privacy);	\
+																								\
+	static void																					\
+	_mrc_ ## NAME ## _finalize(mrc_ ## NAME ## _t client);										\
+																								\
+	static xpc_object_t																			\
+	_mrc_ ## NAME ## _create_start_message(mrc_ ## NAME ## _t client, uint64_t cmd_id);			\
+																								\
+	static void																					\
+	_mrc_ ## NAME ## _handle_start(mrc_ ## NAME ## _t client, xpc_object_t result);				\
+																								\
+	static void																					\
+	_mrc_ ## NAME ## _handle_invalidation(mrc_ ## NAME ## _t client, OSStatus error);			\
+																								\
+	static const struct mrc_client_kind_s _mrc_ ## NAME ## _kind = {							\
+		MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_BEGIN()			\
+		.base = {																				\
+			.superkind			= &_mrc_client_kind,											\
+			.name				= "mrc_" # NAME,												\
+			.copy_description	= _mrc_ ## NAME ## _copy_description,							\
+			.finalize			= _mrc_ ## NAME ## _finalize,									\
+		},																						\
+		.create_start_message	= _mrc_ ## NAME ## _create_start_message,						\
+		.handle_start			= _mrc_ ## NAME ## _handle_start,								\
+		.handle_invalidation	= _mrc_ ## NAME ## _handle_invalidation,						\
+		.operation_name			= OPERATION_NAME,												\
+		__VA_ARGS__																				\
+		MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_END()				\
+	};																							\
+	MRC_OBJECT_SUBKIND_DEFINE_ALLOC(NAME);														\
+	MRC_OBJECT_SUBKIND_DEFINE_NEW_WITH_KIND(NAME, &_mrc_ ## NAME ## _kind.base);				\
+	MRC_BASE_CHECK(NAME, client)
+
+#define MRC_CLIENT_SUBKIND_DEFINE_EX(NAME, OPERATION_NAME, ...)							\
+	static xpc_object_t																	\
+	_mrc_ ## NAME ## _create_stop_message(mrc_ ## NAME ## _t client, uint64_t cmd_id);	\
+																						\
+	static void																			\
+	_mrc_ ## NAME ## _handle_interruption(mrc_ ## NAME ## _t client);					\
+																						\
+	MRC_CLIENT_SUBKIND_DEFINE_CORE(NAME, OPERATION_NAME,								\
+		.create_stop_message	= _mrc_ ## NAME ## _create_stop_message,				\
+		.handle_interruption	= _mrc_ ## NAME ## _handle_interruption,				\
+		.oneshot				= false,												\
+		__VA_ARGS__																		\
+	)
+
+#define MRC_CLIENT_SUBKIND_DEFINE(NAME, OPERATION_NAME) \
+	MRC_CLIENT_SUBKIND_DEFINE_EX(NAME, OPERATION_NAME)
+
+#define MRC_CLIENT_SUBKIND_DEFINE_WITH_NOTIFICATION_HANDLING(NAME, OPERATION_NAME)					\
+	static void																						\
+	_mrc_ ## NAME ## _handle_notification(mrc_ ## NAME ## _t client, xpc_object_t notification);	\
+																									\
+	MRC_CLIENT_SUBKIND_DEFINE_EX(NAME, OPERATION_NAME,												\
+		.handle_notification = _mrc_ ## NAME ## _handle_notification								\
+	)
+
+#define MRC_CLIENT_SUBKIND_DEFINE_ONE_SHOT(NAME, OPERATION_NAME)	\
+	MRC_CLIENT_SUBKIND_DEFINE_CORE(NAME, OPERATION_NAME,			\
+		.oneshot = true,											\
+	)
+
+//======================================================================================================================
+// MARK: - DNS Proxy Kind Definition
+
+struct mrc_dns_proxy_s {
+	struct mrc_client_s				base;			// Object base.
+	xpc_object_t					params;			// DNS proxy parameters.
+	mrc_dns_proxy_event_handler_t	event_handler;	// User's event handler.
+};
+
+MRC_CLIENT_SUBKIND_DEFINE(dns_proxy, "DNS Proxy");
 
 //======================================================================================================================
 
@@ -83,25 +231,81 @@
 MRC_OBJECT_SUBKIND_DEFINE(dns_proxy_state_inquiry);
 
 //======================================================================================================================
+// MARK: - DNS Service Registration Kind Definition
+
+struct mrc_dns_service_registration_s {
+	struct mrc_client_s								base;						// Object base.
+	xpc_object_t									definition_dict;			// DNS service definition as dictionary.
+	mrc_dns_service_registration_event_handler_t	event_handler;				// Event handler.
+	mrc_dns_service_definition_type_t				definition_type;			// The type of the DNS service.
+	bool											reports_connection_errors;	// Whether to report connection error.
+};
+
+MRC_CLIENT_SUBKIND_DEFINE_WITH_NOTIFICATION_HANDLING(dns_service_registration, "DNS Service Registration");
+
+//======================================================================================================================
+// MARK: - Discovery Proxy Kind Definition
+
+struct mrc_discovery_proxy_s {
+	struct mrc_client_s					base;			// Object base.
+	mrc_discovery_proxy_parameters_t	params;			// Discovery proxy parameters.
+	mrc_discovery_proxy_event_handler_t	event_handler;	// Event handler.
+};
+
+MRC_CLIENT_SUBKIND_DEFINE(discovery_proxy, "Discovery Proxy");
+
+//======================================================================================================================
+
+struct mrc_discovery_proxy_parameters_s {
+	struct mdns_obj_s	base;				// Object base.
+	CFMutableArrayRef	server_addresses;	// Server IP addresses.
+	CFMutableSetRef		domains;			// Domains to match in order to use the proxy.
+	xpc_object_t		certs;				// Certificates that can be used as trust anchors for TLS evaluation.
+	uint32_t			ifindex;			// Index of the interface where the proxy can be configured.
+};
+
+MRC_OBJECT_SUBKIND_DEFINE(discovery_proxy_parameters);
+
+//======================================================================================================================
+// MARK: - Cached Local Records Inquiry Kind Definition
+
+struct mrc_cached_local_records_inquiry_s {
+	struct mrc_client_s									base;		// Object base.
+	mrc_cached_local_records_inquiry_result_handler_t	handler;	// User handler.
+};
+
+MRC_CLIENT_SUBKIND_DEFINE_ONE_SHOT(cached_local_records_inquiry, "Cached Local Records Inquiry");
+
+// Keys for record info dictionaries.
+const char * const mrc_cached_local_record_key_first_label		= MRC_CACHED_LOCAL_RECORD_KEY_FIRST_LABEL;
+const char * const mrc_cached_local_record_key_name				= MRC_CACHED_LOCAL_RECORD_KEY_NAME;
+const char * const mrc_cached_local_record_key_rdata			= MRC_CACHED_LOCAL_RECORD_KEY_RDATA;
+const char * const mrc_cached_local_record_key_source_address	= MRC_CACHED_LOCAL_RECORD_KEY_SOURCE_ADDRESS;
+
+//======================================================================================================================
+// MARK: - Record Cache Flush Kind Definition
+
+struct mrc_record_cache_flush_s {
+	struct mrc_client_s						base;			// Object base.
+	mdns_domain_name_t						record_name;	// Record name.
+	mrc_record_cache_flush_result_handler_t	handler;		// User handler.
+	uint16_t								key_tag;		// Key tag.
+	bool									have_key_tag;	// True if the key tag value was set.
+};
+
+MRC_CLIENT_SUBKIND_DEFINE_ONE_SHOT(record_cache_flush, "Record Cache Flush");
+
+//======================================================================================================================
 // MARK: - Local Prototypes
 
-static void
-_mrc_dns_proxy_activate(mrc_dns_proxy_t proxy);
+static mrc_session_t
+_mrc_session_create(mrc_client_t client);
 
 static void
-_mrc_dns_proxy_terminate_async(mrc_dns_proxy_t proxy, OSStatus error);
+_mrc_session_activate_async(mrc_session_t session);
 
 static void
-_mrc_dns_proxy_register(mrc_dns_proxy_t proxy);
-
-static void
-_mrc_dns_proxy_deregister(mrc_dns_proxy_t proxy);
-
-static void
-_mrc_dns_proxy_start(mrc_dns_proxy_t proxy);
-
-static void
-_mrc_dns_proxy_stop(mrc_dns_proxy_t proxy);
+_mrc_session_invalidate_async(mrc_session_t session, OSStatus error);
 
 static void
 _mrc_dns_proxy_state_inquiry_register(mrc_dns_proxy_state_inquiry_t inquiry);
@@ -119,6 +323,15 @@
 _mrc_dns_proxy_state_inquiry_terminate_with_state_description(mrc_dns_proxy_state_inquiry_t inquiry,
 	mdns_xpc_string_t description);
 
+static mrc_discovery_proxy_parameters_t
+_mrc_discovery_proxy_parameters_create_or_copy(mrc_discovery_proxy_parameters_t original);
+
+static xpc_object_t
+_mrc_discovery_proxy_parameters_create_xpc_dictionary(mrc_discovery_proxy_parameters_t params);
+
+static uint64_t
+_mrc_client_get_new_command_id(void);
+
 static os_log_t
 _mrc_client_log(void);
 
@@ -135,10 +348,489 @@
 //======================================================================================================================
 // MARK: - Globals
 
-static mrc_dns_proxy_t g_dns_proxy_list = NULL;
+static mrc_session_t g_session_list = NULL;
 static mrc_dns_proxy_state_inquiry_t g_dns_proxy_state_inquiry_list = NULL;
 
 //======================================================================================================================
+// MARK: - Client Private Methods
+
+static void
+_mrc_client_finalize(const mrc_client_t me)
+{
+	dispatch_forget(&me->user_queue);
+}
+
+//======================================================================================================================
+
+static void
+_mrc_client_set_queue(const mrc_any_client_t any, const dispatch_queue_t queue)
+{
+	const mrc_client_t me = any._mrc_client;
+	mdns_require_return(!me->immutable);
+
+	if (queue) {
+		dispatch_retain(queue);
+	}
+	dispatch_forget(&me->user_queue);
+	me->user_queue = queue;
+}
+
+//======================================================================================================================
+
+static mrc_client_kind_t
+_mrc_client_get_client_kind(const mrc_client_t me)
+{
+	return (mrc_client_kind_t)mrc_get_kind(me);
+}
+
+//======================================================================================================================
+
+static void
+_mrc_client_invalidate_direct(const mrc_client_t me, const OSStatus error)
+{
+	require_return(me->state != mrc_client_state_invalidated);
+
+	if (me->session) {
+		_mrc_session_invalidate_async(me->session, kNoErr);
+		mrc_forget(&me->session);
+	}
+	const mrc_client_kind_t kind = _mrc_client_get_client_kind(me);
+	kind->handle_invalidation(me, error);
+	me->state = mrc_client_state_invalidated;
+}
+
+//======================================================================================================================
+
+static void
+_mrc_client_invalidate_async(const mrc_any_client_t any, const OSStatus error)
+{
+	const mrc_client_t me = any._mrc_client;
+	mrc_retain(me);
+	dispatch_async(_mrc_client_queue(),
+	^{
+		_mrc_client_invalidate_direct(me, error);
+		mrc_release(me);
+	});
+}
+
+//======================================================================================================================
+
+static void
+_mrc_client_activate_direct(const mrc_client_t me)
+{
+	mdns_require_return(me->state == mrc_client_state_nascent);
+
+	me->state = mrc_client_state_activated;
+	me->session = _mrc_session_create(me);
+	if (me->session) {
+		_mrc_session_activate_async(me->session);
+	} else {
+		_mrc_client_invalidate_async(me, kNoResourcesErr);
+	}
+}
+
+//======================================================================================================================
+
+static void
+_mrc_client_activate_async(const mrc_any_client_t any)
+{
+	const mrc_client_t me = any._mrc_client;
+	me->immutable = true;
+	mrc_retain(me);
+	dispatch_async(_mrc_client_queue(),
+	^{
+		_mrc_client_activate_direct(me);
+		mrc_release(me);
+	});
+}
+
+//======================================================================================================================
+
+static xpc_object_t
+_mrc_client_create_start_message(const mrc_client_t me, const uint64_t cmd_id)
+{
+	const mrc_client_kind_t kind = _mrc_client_get_client_kind(me);
+	return kind->create_start_message(me, cmd_id);
+}
+
+//======================================================================================================================
+
+static xpc_object_t
+_mrc_client_create_stop_message(const mrc_client_t me, const uint64_t cmd_id)
+{
+	const mrc_client_kind_t kind = _mrc_client_get_client_kind(me);
+	if (kind->create_stop_message) {
+		return kind->create_stop_message(me, cmd_id);
+	} else {
+		return NULL;
+	}
+}
+
+//======================================================================================================================
+
+static const char *
+_mrc_client_get_operation_name(const mrc_client_t me)
+{
+	const mrc_client_kind_t kind = _mrc_client_get_client_kind(me);
+	return kind->operation_name;
+}
+
+//======================================================================================================================
+
+static void
+_mrc_client_handle_start(const mrc_client_t me, xpc_object_t start_result)
+{
+	mdns_require_return(me->session);
+	const mrc_client_kind_t kind = _mrc_client_get_client_kind(me);
+	kind->handle_start(me, start_result);
+}
+
+//======================================================================================================================
+
+static void
+_mrc_client_handle_interruption(const mrc_client_t me)
+{
+	mdns_require_return(me->session);
+	const mrc_client_kind_t kind = _mrc_client_get_client_kind(me);
+	if (kind->handle_interruption) {
+		kind->handle_interruption(me);
+	}
+}
+
+//======================================================================================================================
+
+static bool
+_mrc_client_handle_notification(const mrc_client_t me, const xpc_object_t notification)
+{
+	bool handled = false;
+	const mrc_client_kind_t kind = _mrc_client_get_client_kind(me);
+	if (kind->handle_notification) {
+		kind->handle_notification(me, notification);
+		handled = true;
+	}
+	return handled;
+}
+
+//======================================================================================================================
+
+static void
+_mrc_client_handle_error(const mrc_client_t me, const OSStatus error)
+{
+	mdns_require_return(me->session);
+	_mrc_client_invalidate_async(me, error);
+}
+
+//======================================================================================================================
+
+static bool
+_mrc_client_is_oneshot(const mrc_client_t me)
+{
+	const mrc_client_kind_t kind = _mrc_client_get_client_kind(me);
+	return kind->oneshot;
+}
+
+//======================================================================================================================
+
+static dispatch_queue_t
+_mrc_client_get_user_queue(const mrc_any_client_t any)
+{
+	const mrc_client_t me = any._mrc_client;
+	return me->user_queue;
+}
+
+//======================================================================================================================
+
+static bool
+_mrc_client_is_immutable(const mrc_any_client_t any)
+{
+	const mrc_client_t me = any._mrc_client;
+	return me->immutable;
+}
+
+//======================================================================================================================
+// MARK: - Session Private Methods
+
+static mrc_session_t
+_mrc_session_create(const mrc_client_t client)
+{
+	mrc_session_t session = NULL;
+	mrc_session_t obj = _mrc_session_new();
+	mdns_require_quiet(obj, exit);
+
+	obj->client = client;
+	mrc_retain(obj->client);
+	obj->entity_name = _mrc_client_get_operation_name(obj->client);
+	session = obj;
+	obj = NULL;
+
+exit:
+	mrc_forget(&obj);
+	return session;
+}
+
+//======================================================================================================================
+
+static char *
+_mrc_session_copy_description(const mrc_session_t me, const bool debug, __unused const bool privacy)
+{
+	char *description = NULL;
+	mdns_string_builder_t sb = mdns_string_builder_create(0, NULL);
+	mdns_require_quiet(sb, exit);
+
+	OSStatus err;
+	if (debug) {
+		const mdns_kind_t kind = mrc_get_kind(me);
+		err = mdns_string_builder_append_formatted(sb, "<%s: %p>: ", kind->name, (void *)me);
+		mdns_require_noerr_quiet(err, exit);
+	}
+	err = mdns_string_builder_append_formatted(sb, "entity: %s", me->entity_name);
+	mdns_require_noerr_quiet(err, exit);
+
+	description = mdns_string_builder_copy_string(sb);
+	mdns_require_quiet(description, exit);
+
+exit:
+	mdns_forget(&sb);
+	return description;
+}
+
+//======================================================================================================================
+
+static void
+_mrc_session_finalize(const mrc_session_t me)
+{
+	mrc_forget(&me->client);
+}
+
+//======================================================================================================================
+
+static void
+_mrc_session_handle_stop_reply(const mrc_session_t me, const xpc_object_t reply)
+{
+	if (xpc_get_type(reply) == XPC_TYPE_DICTIONARY) {
+		bool valid;
+		OSStatus err = mrc_xpc_message_get_error(reply, &valid);
+		if (!valid) {
+			err = kResponseErr;
+		}
+		os_log_with_type(_mrc_client_log(), err ? OS_LOG_TYPE_ERROR : OS_LOG_TYPE_INFO,
+			"[S%" PRIu64 "] %{public}s stop reply -- error: %{mdns:err}ld", me->cmd_id, me->entity_name, (long)err);
+	} else {
+		char *description = xpc_copy_description(reply);
+		os_log_error(_mrc_client_log(),
+			"[S%" PRIu64 "] Abnormal %{public}s stop reply: %{public}s", me->cmd_id, me->entity_name, description);
+		ForgetMem(&description);
+	}
+}
+
+//======================================================================================================================
+
+static void
+_mrc_session_send_stop_message(const mrc_session_t me)
+{
+	xpc_object_t msg = _mrc_client_create_stop_message(me->client, me->cmd_id);
+	mdns_require_return(msg);
+
+	mrc_retain(me);
+	xpc_connection_send_message_with_reply(_mrc_client_connection(), msg, _mrc_client_queue(),
+	^(const xpc_object_t reply)
+	{
+		_mrc_session_handle_stop_reply(me, reply);
+		mrc_release(me);
+	});
+	xpc_forget(&msg);
+}
+
+//======================================================================================================================
+
+static void
+_mrc_session_invalidate_direct(const mrc_session_t me, const OSStatus error)
+{
+	mdns_require_return(me->state != mrc_session_state_invalidated);
+
+	mrc_session_t *ptr = &g_session_list;
+	while (*ptr && (*ptr != me)) {
+		ptr = &(*ptr)->next;
+	}
+	if (*ptr) {
+		mrc_release(*ptr);
+		*ptr = me->next;
+		me->next = NULL;
+		switch (me->state) {
+			case mrc_session_state_starting:
+			case mrc_session_state_started:
+				_mrc_session_send_stop_message(me);
+				break;
+
+			case mrc_session_state_done:
+			case mrc_session_state_invalidated:
+			case mrc_session_state_nascent:
+			MDNS_COVERED_SWITCH_DEFAULT:
+				break;
+		}
+		me->state = mrc_session_state_invalidated;
+		_mrc_client_handle_error(me->client, error);
+		mrc_forget(&me->client);
+	}
+}
+
+//======================================================================================================================
+
+static void
+_mrc_session_invalidate_async(const mrc_session_t me, const OSStatus error)
+{
+	mrc_retain(me);
+	dispatch_async(_mrc_client_queue(),
+	^{
+		_mrc_session_invalidate_direct(me, error);
+		mrc_release(me);
+	});
+}
+
+//======================================================================================================================
+
+static void
+_mrc_session_terminate(const mrc_session_t me, const OSStatus error)
+{
+	me->state = mrc_session_state_done;
+	_mrc_session_invalidate_async(me, error);
+}
+
+//======================================================================================================================
+
+static void
+_mrc_session_handle_start_reply(const mrc_session_t me, const uint64_t cmd_id, const xpc_object_t reply)
+{
+	mdns_require_return(me->state == mrc_session_state_starting);
+	mdns_require_return(me->cmd_id == cmd_id);
+
+	if (xpc_get_type(reply) == XPC_TYPE_DICTIONARY) {
+		bool valid;
+		OSStatus err = mrc_xpc_message_get_error(reply, &valid);
+		if (!valid) {
+			err = kResponseErr;
+		}
+		os_log_with_type(_mrc_client_log(), err ? OS_LOG_TYPE_ERROR : OS_LOG_TYPE_INFO,
+			"[S%" PRIu64 "] %{public}s start reply -- error: %{mdns:err}ld", me->cmd_id, me->entity_name, (long)err);
+		if (!err) {
+			_mrc_client_handle_start(me->client, mrc_xpc_message_get_result(reply));
+		}
+		if (err || _mrc_client_is_oneshot(me->client)) {
+			_mrc_session_terminate(me, err);
+		} else {
+			me->state = mrc_session_state_started;
+		}
+	} else {
+		char *description = xpc_copy_description(reply);
+		os_log_error(_mrc_client_log(),
+			"[S%" PRIu64 "] Abnormal %{public}s start reply: %{public}s", me->cmd_id, me->entity_name, description);
+		ForgetMem(&description);
+		if (reply != XPC_ERROR_CONNECTION_INTERRUPTED) {
+			const OSStatus err = (reply == XPC_ERROR_CONNECTION_INVALID) ? kConnectionErr : kResponseErr;
+			_mrc_session_terminate(me, err);
+		}
+	}
+}
+
+//======================================================================================================================
+
+static void
+_mrc_session_send_start_message(const mrc_session_t me)
+{
+	me->state = mrc_session_state_starting;
+	me->cmd_id = _mrc_client_get_new_command_id();
+	xpc_object_t msg = _mrc_client_create_start_message(me->client, me->cmd_id);
+	mdns_require_quiet(msg, exit);
+
+	mrc_retain(me);
+	const uint64_t cmd_id = me->cmd_id;
+	xpc_connection_send_message_with_reply(_mrc_client_connection(), msg, _mrc_client_queue(),
+	^(const xpc_object_t reply)
+	{
+		_mrc_session_handle_start_reply(me, cmd_id, reply);
+		mrc_release(me);
+	});
+
+exit:
+	xpc_forget(&msg);
+}
+
+//======================================================================================================================
+
+static void
+_mrc_session_activate_direct(const mrc_session_t me)
+{
+	mdns_require_return(me->state == mrc_session_state_nascent);
+
+	mrc_session_t *ptr = &g_session_list;
+	while (*ptr) {
+		ptr = &(*ptr)->next;
+	}
+	*ptr = me;
+	mrc_retain(*ptr);
+	_mrc_session_send_start_message(me);
+}
+
+//======================================================================================================================
+
+static void
+_mrc_session_activate_async(const mrc_session_t me)
+{
+	mrc_retain(me);
+	dispatch_async(_mrc_client_queue(),
+	^{
+		_mrc_session_activate_direct(me);
+		mrc_release(me);
+	});
+}
+
+//======================================================================================================================
+
+static void
+_mrc_session_handle_connection_interruption(const mrc_session_t me)
+{
+	switch (me->state) {
+		case mrc_session_state_starting:
+		case mrc_session_state_started:
+			if (me->state == mrc_session_state_started) {
+				_mrc_client_handle_interruption(me->client);
+			}
+			_mrc_session_send_start_message(me);
+			break;
+
+		case mrc_session_state_done:
+		case mrc_session_state_invalidated:
+		case mrc_session_state_nascent:
+		MDNS_COVERED_SWITCH_DEFAULT:
+			break;
+	}
+}
+
+//======================================================================================================================
+
+static void
+_mrc_session_handle_notification(const mrc_session_t me, const xpc_object_t notification)
+{
+	const xpc_object_t notification_body = mrc_xpc_notification_get_body(notification);
+	if (notification_body) {
+		const bool handled = _mrc_client_handle_notification(me->client, notification_body);
+		if (!handled) {
+			char *description = xpc_copy_description(notification_body);
+			os_log_fault(_mrc_client_log(),
+				"[S%" PRIu64 "] Notification for %{public}s was unhandled: %{private}s",
+				me->cmd_id, me->entity_name, description);
+			ForgetMem(&description);
+		}
+	} else {
+		char *description = xpc_copy_description(notification);
+		os_log_fault(_mrc_client_log(),
+			"[S%" PRIu64 "] Notification for %{public}s is missing body: %{private}s",
+			me->cmd_id, me->entity_name, description);
+		ForgetMem(&description);
+	}
+}
+
+//======================================================================================================================
 // MARK: - DNS Proxy Public Methods
 
 mrc_dns_proxy_t
@@ -169,12 +861,7 @@
 void
 mrc_dns_proxy_set_queue(const mrc_dns_proxy_t me, const dispatch_queue_t queue)
 {
-	require_return(!me->immutable);
-	if (queue) {
-		dispatch_retain(queue);
-	}
-	dispatch_forget(&me->queue);
-	me->queue = queue;
+	_mrc_client_set_queue(me, queue);
 }
 
 //======================================================================================================================
@@ -182,7 +869,8 @@
 void
 mrc_dns_proxy_set_event_handler(const mrc_dns_proxy_t me, const mrc_dns_proxy_event_handler_t handler)
 {
-	require_return(!me->immutable);
+	mdns_require_return(!_mrc_client_is_immutable(me));
+
 	const mrc_dns_proxy_event_handler_t new_handler = handler ? Block_copy(handler) : NULL;
 	BlockForget(&me->event_handler);
 	me->event_handler = new_handler;
@@ -193,13 +881,7 @@
 void
 mrc_dns_proxy_activate(const mrc_dns_proxy_t me)
 {
-	me->immutable = true;
-	mrc_retain(me);
-	dispatch_async(_mrc_client_queue(),
-	^{
-		_mrc_dns_proxy_activate(me);
-		mrc_release(me);
-	});
+	_mrc_client_activate_async(me);
 }
 
 //======================================================================================================================
@@ -207,8 +889,7 @@
 void
 mrc_dns_proxy_invalidate(const mrc_dns_proxy_t me)
 {
-	me->immutable = true;
-	_mrc_dns_proxy_terminate_async(me, kNoErr);
+	_mrc_client_invalidate_async(me, kNoErr);
 }
 
 //======================================================================================================================
@@ -231,7 +912,7 @@
 	} while(0)
 
 	if (debug) {
-		_do_appendf("<%s: %p>: ", me->base.kind->name, me);
+		_do_appendf("<%s: %p>: ", mrc_get_kind(me)->name, me);
 	}
 #undef _do_appendf
 	size_t wrote_len, desc_full_len;
@@ -286,19 +967,23 @@
 static void
 _mrc_dns_proxy_finalize(const mrc_dns_proxy_t me)
 {
-	dispatch_forget(&me->queue);
 	xpc_forget(&me->params);
 }
 
 //======================================================================================================================
 
-static void
-_mrc_dns_proxy_activate(const mrc_dns_proxy_t me)
+static xpc_object_t
+_mrc_dns_proxy_create_start_message(const mrc_dns_proxy_t me, const uint64_t cmd_id)
 {
-	require_return(me->state == mrc_dns_proxy_state_nascent);
+	return mrc_xpc_create_dns_proxy_start_command_message(cmd_id, me->params);
+}
 
-	_mrc_dns_proxy_register(me);
-	_mrc_dns_proxy_start(me);
+//======================================================================================================================
+
+static xpc_object_t
+_mrc_dns_proxy_create_stop_message(__unused const mrc_dns_proxy_t me, const uint64_t cmd_id)
+{
+	return mrc_xpc_create_dns_proxy_stop_command_message(cmd_id);
 }
 
 //======================================================================================================================
@@ -307,15 +992,19 @@
 _mrc_dns_proxy_generate_event_with_error(const mrc_dns_proxy_t me, const mrc_dns_proxy_event_t event,
 	const OSStatus error)
 {
-	require_return(me->queue && me->event_handler);
+	const dispatch_queue_t user_queue = _mrc_client_get_user_queue(me);
+	const mrc_dns_proxy_event_handler_t event_handler = me->event_handler;
+	mdns_require_quiet(user_queue && event_handler, exit);
 
-	const mrc_dns_proxy_event_handler_t event_handler = Block_copy(me->event_handler);
-	dispatch_async(me->queue,
+	dispatch_async(user_queue,
 	^{
 		event_handler(event, error);
-		mrc_dns_proxy_event_handler_t tmp = event_handler;
-		BlockForget(&tmp);
 	});
+
+exit:
+	if (event == mrc_dns_proxy_event_invalidation) {
+		BlockForget(&me->event_handler);
+	}
 }
 
 //======================================================================================================================
@@ -328,169 +1017,26 @@
 
 //======================================================================================================================
 
-static uint64_t
-_mrc_client_get_new_command_id(void)
+static void
+_mrc_dns_proxy_handle_start(const mrc_dns_proxy_t me, __unused const xpc_object_t result)
 {
-	static uint64_t last_command_id = 0;
-	return ++last_command_id;
+	_mrc_dns_proxy_generate_event(me, mrc_dns_proxy_event_started);
 }
 
 //======================================================================================================================
 
 static void
-_mrc_dns_proxy_register(const mrc_dns_proxy_t me)
+_mrc_dns_proxy_handle_interruption(const mrc_dns_proxy_t me)
 {
-	require_return(me->cmd_id == 0);
-
-	me->cmd_id = _mrc_client_get_new_command_id();
-	mrc_dns_proxy_t *ptr = &g_dns_proxy_list;
-	while (*ptr) {
-		ptr = &(*ptr)->next;
-	}
-	*ptr = me;
-	mrc_retain(*ptr);
+	_mrc_dns_proxy_generate_event(me, mrc_dns_proxy_event_interruption);
 }
 
 //======================================================================================================================
 
 static void
-_mrc_dns_proxy_deregister(const mrc_dns_proxy_t me)
+_mrc_dns_proxy_handle_invalidation(const mrc_dns_proxy_t me, const OSStatus error)
 {
-	mrc_dns_proxy_t *ptr = &g_dns_proxy_list;
-	while (*ptr && (*ptr != me)) {
-		ptr = &(*ptr)->next;
-	}
-	if (*ptr) {
-		mrc_release(*ptr);
-		*ptr = me->next;
-		me->next = NULL;
-	}
-}
-
-static void
-_mrcs_dns_proxy_handle_dns_proxy_start_reply(const mrc_dns_proxy_t me, const uint64_t cmd_id, const xpc_object_t reply)
-{
-	require_return(me->cmd_id == cmd_id);
-	require_return(me->state == mrc_dns_proxy_state_starting);
-
-	if (xpc_get_type(reply) == XPC_TYPE_DICTIONARY) {
-		bool valid;
-		OSStatus err = mrc_xpc_message_get_error(reply, &valid);
-		if (!valid) {
-			err = kResponseErr;
-		}
-		os_log_with_type(_mrc_client_log(), err ? OS_LOG_TYPE_ERROR : OS_LOG_TYPE_INFO,
-			"[DP%llu] DNS proxy start reply -- error: %{mdns:err}ld", (unsigned long long)me->cmd_id, (long)err);
-		if (!err) {
-			me->state = mrc_dns_proxy_state_started;
-			_mrc_dns_proxy_generate_event(me, mrc_dns_proxy_event_started);
-		} else {
-			me->state = mrc_dns_proxy_state_failed;
-			_mrc_dns_proxy_terminate_async(me, err);
-		}
-	} else {
-		char *description = xpc_copy_description(reply);
-		os_log_error(_mrc_client_log(),
-			"[DP%llu] Abnormal DNS proxy start reply: %{public}s", (unsigned long long)me->cmd_id, description);
-		ForgetMem(&description);
-		if (reply != XPC_ERROR_CONNECTION_INTERRUPTED) {
-			const OSStatus err = (reply == XPC_ERROR_CONNECTION_INVALID) ? kConnectionErr : kResponseErr;
-			me->state = mrc_dns_proxy_state_failed;
-			_mrc_dns_proxy_terminate_async(me, err);
-		}
-	}
-}
-
-//======================================================================================================================
-
-static void
-_mrc_dns_proxy_start(const mrc_dns_proxy_t me)
-{
-	me->state = mrc_dns_proxy_state_starting;
-	xpc_object_t msg = mrc_xpc_create_dns_proxy_start_command_message(me->cmd_id, me->params);
-	mrc_retain(me);
-	const uint64_t cmd_id = me->cmd_id;
-	xpc_connection_send_message_with_reply(_mrc_client_connection(), msg, _mrc_client_queue(),
-	^(const xpc_object_t reply)
-	{
-		_mrcs_dns_proxy_handle_dns_proxy_start_reply(me, cmd_id, reply);
-		mrc_release(me);
-	});
-	xpc_forget(&msg);
-}
-
-//======================================================================================================================
-
-static void
-_mrcs_dns_proxy_handle_dns_proxy_stop_reply(const mrc_dns_proxy_t me, const xpc_object_t reply)
-{
-	if (xpc_get_type(reply) == XPC_TYPE_DICTIONARY) {
-		bool valid;
-		OSStatus err = mrc_xpc_message_get_error(reply, &valid);
-		if (!valid) {
-			err = kResponseErr;
-		}
-		os_log_with_type(_mrc_client_log(), err ? OS_LOG_TYPE_ERROR : OS_LOG_TYPE_INFO,
-			"[DP%llu] DNS proxy stop reply -- error: %{mdns:err}ld", (unsigned long long)me->cmd_id, (long)err);
-	} else {
-		char *description = xpc_copy_description(reply);
-		os_log_error(_mrc_client_log(),
-			"[DP%llu] Abnormal DNS proxy stop reply: %{public}s", (unsigned long long)me->cmd_id, description);
-		ForgetMem(&description);
-	}
-}
-
-//======================================================================================================================
-
-static void
-_mrc_dns_proxy_stop(const mrc_dns_proxy_t me)
-{
-	xpc_object_t msg = mrc_xpc_create_dns_proxy_stop_command_message(me->cmd_id);
-	mrc_retain(me);
-	xpc_connection_send_message_with_reply(_mrc_client_connection(), msg, _mrc_client_queue(),
-	^(const xpc_object_t reply)
-	{
-		_mrcs_dns_proxy_handle_dns_proxy_stop_reply(me, reply);
-		mrc_release(me);
-	});
-	xpc_forget(&msg);
-}
-
-//======================================================================================================================
-
-static void
-_mrc_dns_proxy_terminate_direct(const mrc_dns_proxy_t me, const OSStatus error)
-{
-	require_return(me->state != mrc_dns_proxy_state_invalidated);
-
-	_mrc_dns_proxy_deregister(me);
-	switch (me->state) {
-		case mrc_dns_proxy_state_starting:
-		case mrc_dns_proxy_state_started:
-			_mrc_dns_proxy_stop(me);
-			break;
-
-		case mrc_dns_proxy_state_failed:
-		case mrc_dns_proxy_state_invalidated:
-		case mrc_dns_proxy_state_nascent:
-			break;
-	}
-	me->state = mrc_dns_proxy_state_invalidated;
 	_mrc_dns_proxy_generate_event_with_error(me, mrc_dns_proxy_event_invalidation, error);
-	BlockForget(&me->event_handler);
-}
-
-//======================================================================================================================
-
-static void
-_mrc_dns_proxy_terminate_async(const mrc_dns_proxy_t me, const OSStatus error)
-{
-	mrc_retain(me);
-	dispatch_async(_mrc_client_queue(),
-	^{
-		_mrc_dns_proxy_terminate_direct(me, error);
-		mrc_release(me);
-	});
 }
 
 //======================================================================================================================
@@ -897,8 +1443,1093 @@
 }
 
 //======================================================================================================================
+// MARK: - DNS Service Registration Public Methods
+
+mrc_dns_service_registration_t
+mrc_dns_service_registration_create(const mdns_dns_service_definition_t definition)
+{
+	mrc_dns_service_registration_t registration = NULL;
+	mrc_dns_service_registration_t obj = _mrc_dns_service_registration_new();
+	mdns_require_quiet(obj, exit);
+
+	obj->definition_dict = mdns_dns_service_definition_create_xpc_dictionary(definition);
+	mdns_require_quiet(obj->definition_dict, exit);
+
+	obj->definition_type = mrc_dns_service_definition_type_do53;
+	registration = obj;
+	obj = NULL;
+
+exit:
+	mrc_forget(&obj);
+	return registration;
+}
+
+//======================================================================================================================
+
+mrc_dns_service_registration_t
+mrc_dns_service_registration_create_push(const mdns_dns_push_service_definition_t definition)
+{
+	mrc_dns_service_registration_t registration = NULL;
+	mrc_dns_service_registration_t obj = _mrc_dns_service_registration_new();
+	mdns_require_quiet(obj, exit);
+
+	obj->definition_dict = mdns_dns_push_service_definition_create_xpc_dictionary(definition);
+	mdns_require_quiet(obj->definition_dict, exit);
+
+	obj->definition_type = mrc_dns_service_definition_type_push;
+	registration = obj;
+	obj = NULL;
+
+exit:
+	mrc_forget(&obj);
+	return registration;
+}
+
+//======================================================================================================================
+
+void
+mrc_dns_service_registration_set_reports_connection_errors(const mrc_dns_service_registration_t me,
+	const bool reports_connection_errors)
+{
+	mdns_require_return(!_mrc_client_is_immutable(me));
+	me->reports_connection_errors = reports_connection_errors;
+}
+
+//======================================================================================================================
+
+void
+mrc_dns_service_registration_set_queue(const mrc_dns_service_registration_t me, const dispatch_queue_t queue)
+{
+	_mrc_client_set_queue(me, queue);
+}
+
+//======================================================================================================================
+
+void
+mrc_dns_service_registration_set_event_handler(const mrc_dns_service_registration_t me,
+	const mrc_dns_service_registration_event_handler_t handler)
+{
+	mdns_require_return(!_mrc_client_is_immutable(me));
+
+	const mrc_dns_service_registration_event_handler_t new_handler = handler ? Block_copy(handler) : NULL;
+	BlockForget(&me->event_handler);
+	me->event_handler = new_handler;
+}
+
+//======================================================================================================================
+
+void
+mrc_dns_service_registration_activate(const mrc_dns_service_registration_t me)
+{
+	_mrc_client_activate_async(me);
+}
+
+//======================================================================================================================
+
+void
+mrc_dns_service_registration_invalidate(const mrc_dns_service_registration_t me)
+{
+	_mrc_client_invalidate_async(me, kNoErr);
+}
+
+//======================================================================================================================
+// MARK: - DNS Service Registration Private Methods
+
+static char *
+_mrc_dns_service_registration_copy_description(const mrc_dns_service_registration_t me, const bool debug,
+	__unused const bool privacy)
+{
+	char *description = NULL;
+	mdns_string_builder_t sb = mdns_string_builder_create(0, NULL);
+	mdns_require_quiet(sb, exit);
+
+	OSStatus err;
+	if (debug) {
+		const mdns_kind_t kind = mrc_get_kind(me);
+		err = mdns_string_builder_append_formatted(sb, "<%s: %p>: ", kind->name, (void *)me);
+		mdns_require_noerr_quiet(err, exit);
+	}
+	description = mdns_string_builder_copy_string(sb);
+	mdns_require_quiet(description, exit);
+
+exit:
+	mdns_forget(&sb);
+	return description;
+}
+
+//======================================================================================================================
+
+static void
+_mrc_dns_service_registration_finalize(const mrc_dns_service_registration_t me)
+{
+	xpc_forget(&me->definition_dict);
+}
+
+//======================================================================================================================
+
+static xpc_object_t
+_mrc_dns_service_registration_create_start_message(const mrc_dns_service_registration_t me, const uint64_t cmd_id)
+{
+	xpc_object_t params = xpc_dictionary_create_empty();
+	mrc_xpc_dns_service_registration_params_set_defintion_dictionary(params, me->definition_dict);
+	mrc_xpc_dns_service_registration_params_set_definition_type(params, me->definition_type);
+	mrc_xpc_dns_service_registration_params_set_reports_connection_errors(params,
+		me->reports_connection_errors);
+	const xpc_object_t msg = mrc_xpc_create_dns_service_registration_start_command_message(cmd_id, params);
+	xpc_forget(&params);
+	return msg;
+}
+
+//======================================================================================================================
+
+static xpc_object_t
+_mrc_dns_service_registration_create_stop_message(__unused const mrc_dns_service_registration_t me,
+	const uint64_t cmd_id)
+{
+	return mrc_xpc_create_dns_service_registration_stop_command_message(cmd_id);
+}
+
+//======================================================================================================================
+
+static void
+_mrc_dns_service_registration_generate_event_with_error(const mrc_dns_service_registration_t me,
+	const mrc_dns_service_registration_event_t event, const OSStatus error)
+{
+	const dispatch_queue_t user_queue = _mrc_client_get_user_queue(me);
+	const mrc_dns_service_registration_event_handler_t event_handler = me->event_handler;
+	mdns_require_quiet(user_queue && event_handler, exit);
+
+	dispatch_async(user_queue,
+	^{
+		event_handler(event, error);
+	});
+
+exit:
+	if (event == mrc_dns_service_registration_event_invalidation) {
+		BlockForget(&me->event_handler);
+	}
+}
+
+//======================================================================================================================
+
+static void
+_mrc_dns_service_registration_generate_event(const mrc_dns_service_registration_t me,
+	const mrc_dns_service_registration_event_t event)
+{
+	_mrc_dns_service_registration_generate_event_with_error(me, event, kNoErr);
+}
+
+//======================================================================================================================
+
+static void
+_mrc_dns_service_registration_handle_start(const mrc_dns_service_registration_t me, __unused const xpc_object_t result)
+{
+	_mrc_dns_service_registration_generate_event(me, mrc_dns_service_registration_event_started);
+}
+
+//======================================================================================================================
+
+static void
+_mrc_dns_service_registration_handle_notification(const mrc_dns_service_registration_t me,
+	const xpc_object_t notification)
+{
+	if (!me->reports_connection_errors) {
+		char *description = xpc_copy_description(notification);
+		os_log_fault(_mrc_client_log(),
+			"Current DNS service registration didn't require error reporting, ignoring -- "
+			"registration: %@, notification: %{private}s",	me, description);
+		ForgetMem(&description);
+		return;
+	}
+
+	bool valid;
+	const OSStatus connection_error = mrc_xpc_dns_service_registration_notification_get_connection_error(notification,
+		&valid);
+	mdns_require_return(valid);
+
+	_mrc_dns_service_registration_generate_event_with_error(me, mrc_dns_service_registration_event_connection_error,
+		connection_error);
+}
+
+//======================================================================================================================
+
+static void
+_mrc_dns_service_registration_handle_interruption(const mrc_dns_service_registration_t me)
+{
+	_mrc_dns_service_registration_generate_event(me, mrc_dns_service_registration_event_interruption);
+}
+
+//======================================================================================================================
+
+static void
+_mrc_dns_service_registration_handle_invalidation(const mrc_dns_service_registration_t me, const OSStatus error)
+{
+	_mrc_dns_service_registration_generate_event_with_error(me, mrc_dns_service_registration_event_invalidation, error);
+}
+
+//======================================================================================================================
+// MARK: - Discovery Proxy Public Methods
+
+mrc_discovery_proxy_t
+mrc_discovery_proxy_create(const mrc_discovery_proxy_parameters_t params)
+{
+	mrc_discovery_proxy_t proxy = NULL;
+	mrc_discovery_proxy_t obj = _mrc_discovery_proxy_new();
+	mdns_require_quiet(obj, exit);
+
+	obj->params = _mrc_discovery_proxy_parameters_create_or_copy(params);
+	mdns_require_quiet(obj->params, exit);
+
+	proxy = obj;
+	obj = NULL;
+
+exit:
+	mrc_forget(&obj);
+	return proxy;
+}
+
+//======================================================================================================================
+
+void
+mrc_discovery_proxy_set_queue(const mrc_discovery_proxy_t me, const dispatch_queue_t queue)
+{
+	_mrc_client_set_queue(me, queue);
+}
+
+//======================================================================================================================
+
+void
+mrc_discovery_proxy_set_event_handler(const mrc_discovery_proxy_t me, const mrc_discovery_proxy_event_handler_t handler)
+{
+	mdns_require_return(!_mrc_client_is_immutable(me));
+
+	const mrc_discovery_proxy_event_handler_t new_handler = handler ? Block_copy(handler) : NULL;
+	BlockForget(&me->event_handler);
+	me->event_handler = new_handler;
+}
+
+//======================================================================================================================
+
+void
+mrc_discovery_proxy_activate(const mrc_discovery_proxy_t me)
+{
+	_mrc_client_activate_async(me);
+}
+
+//======================================================================================================================
+
+void
+mrc_discovery_proxy_invalidate(const mrc_discovery_proxy_t me)
+{
+	_mrc_client_invalidate_async(me, kNoErr);
+}
+
+//======================================================================================================================
+// MARK: - Discovery Proxy Private Methods
+
+static char *
+_mrc_discovery_proxy_copy_description(const mrc_discovery_proxy_t me, const bool debug, const bool privacy)
+{
+	char *description = NULL;
+	char *params_desc = NULL;
+	mdns_string_builder_t sb = mdns_string_builder_create(0, NULL);
+	mdns_require_quiet(sb, exit);
+
+	OSStatus err;
+	if (debug) {
+		const mdns_kind_t kind = mrc_get_kind(me);
+		err = mdns_string_builder_append_formatted(sb, "<%s: %p>: ", kind->name, (void *)me);
+		mdns_require_noerr_quiet(err, exit);
+	}
+	params_desc = _mrc_discovery_proxy_parameters_copy_description(me->params, false, privacy);
+	mdns_require_quiet(params_desc, exit);
+
+	err = mdns_string_builder_append_formatted(sb, "%s", params_desc);
+	mdns_require_noerr_quiet(err, exit);
+
+	description = mdns_string_builder_copy_string(sb);
+	mdns_require_quiet(description, exit);
+
+exit:
+	ForgetMem(&params_desc);
+	mdns_forget(&sb);
+	return description;
+}
+
+//======================================================================================================================
+
+static void
+_mrc_discovery_proxy_finalize(const mrc_discovery_proxy_t me)
+{
+	mrc_forget(&me->params);
+}
+
+//======================================================================================================================
+
+static xpc_object_t
+_mrc_discovery_proxy_create_start_message(const mrc_discovery_proxy_t me, const uint64_t cmd_id)
+{
+	xpc_object_t start_msg = NULL;
+	xpc_object_t params_dict = _mrc_discovery_proxy_parameters_create_xpc_dictionary(me->params);
+	if (params_dict) {
+		start_msg = mrc_xpc_create_discovery_proxy_start_command_message(cmd_id, params_dict);
+		xpc_forget(&params_dict);
+	}
+	return start_msg;
+}
+
+//======================================================================================================================
+
+static xpc_object_t
+_mrc_discovery_proxy_create_stop_message(__unused const mrc_discovery_proxy_t me, const uint64_t cmd_id)
+{
+	return mrc_xpc_create_discovery_proxy_stop_command_message(cmd_id);
+}
+
+//======================================================================================================================
+
+static void
+_mrc_discovery_proxy_generate_event_with_error(const mrc_discovery_proxy_t me, const mrc_discovery_proxy_event_t event,
+	const OSStatus error)
+{
+	const dispatch_queue_t user_queue = _mrc_client_get_user_queue(me);
+	const mrc_discovery_proxy_event_handler_t event_handler = me->event_handler;
+	mdns_require_quiet(user_queue && event_handler, exit);
+
+	dispatch_async(user_queue,
+	^{
+		event_handler(event, error);
+	});
+
+exit:
+	if (event == mrc_discovery_proxy_event_invalidation) {
+		BlockForget(&me->event_handler);
+	}
+}
+
+//======================================================================================================================
+
+static void
+_mrc_discovery_proxy_generate_event(const mrc_discovery_proxy_t me, const mrc_discovery_proxy_event_t event)
+{
+	_mrc_discovery_proxy_generate_event_with_error(me, event, kNoErr);
+}
+
+//======================================================================================================================
+
+static void
+_mrc_discovery_proxy_handle_start(const mrc_discovery_proxy_t me, __unused const xpc_object_t result)
+{
+	_mrc_discovery_proxy_generate_event(me, mrc_discovery_proxy_event_started);
+}
+
+//======================================================================================================================
+
+static void
+_mrc_discovery_proxy_handle_interruption(const mrc_discovery_proxy_t me)
+{
+	_mrc_discovery_proxy_generate_event(me, mrc_discovery_proxy_event_interruption);
+}
+
+//======================================================================================================================
+
+static void
+_mrc_discovery_proxy_handle_invalidation(const mrc_discovery_proxy_t me, const OSStatus error)
+{
+	_mrc_discovery_proxy_generate_event_with_error(me, mrc_discovery_proxy_event_invalidation, error);
+}
+
+//======================================================================================================================
+// MARK: - Discovery Proxy Parameters Public Methods
+
+mrc_discovery_proxy_parameters_t
+mrc_discovery_proxy_parameters_create(void)
+{
+	return _mrc_discovery_proxy_parameters_create_or_copy(NULL);
+}
+
+//======================================================================================================================
+
+void
+mrc_discovery_proxy_parameters_set_interface(const mrc_discovery_proxy_parameters_t me, const uint32_t ifindex)
+{
+	me->ifindex = ifindex;
+}
+
+//======================================================================================================================
+
+OSStatus
+mrc_discovery_proxy_parameters_add_server_ipv4_address(const mrc_discovery_proxy_parameters_t me,
+	const uint32_t ipv4_address, const uint16_t port)
+{
+	OSStatus err;
+	mdns_address_t address = mdns_address_create_ipv4(ipv4_address, port);
+	mdns_require_action_quiet(address, exit, err = kNoResourcesErr);
+
+	CFArrayAppendValue(me->server_addresses, address);
+	err = kNoErr;
+
+exit:
+	mdns_forget(&address);
+	return err;
+}
+
+//======================================================================================================================
+
+OSStatus
+mrc_discovery_proxy_parameters_add_server_ipv6_address(const mrc_discovery_proxy_parameters_t me,
+	const uint8_t ipv6_address[static 16], const uint16_t port, const uint32_t scope_id)
+{
+	OSStatus err;
+	mdns_address_t address = mdns_address_create_ipv6(ipv6_address, port, scope_id);
+	mdns_require_action_quiet(address, exit, err = kNoResourcesErr);
+
+	CFArrayAppendValue(me->server_addresses, address);
+	err = kNoErr;
+
+exit:
+	mdns_forget(&address);
+	return err;
+}
+
+//======================================================================================================================
+
+OSStatus
+mrc_discovery_proxy_parameters_add_match_domain(const mrc_discovery_proxy_parameters_t me,
+	const char * const domain_str)
+{
+	OSStatus err;
+	mdns_domain_name_t domain = mdns_domain_name_create(domain_str, mdns_domain_name_create_opts_none, &err);
+	mdns_require_noerr_quiet(err, exit);
+
+	CFSetAddValue(me->domains, domain);
+
+exit:
+	mdns_forget(&domain);
+	return err;
+}
+
+//======================================================================================================================
+
+void
+mrc_discovery_proxy_parameters_add_server_certificate(const mrc_discovery_proxy_parameters_t me,
+	const uint8_t * const cert_data, const size_t cert_len)
+{
+	mdns_xpc_array_append_data(me->certs, cert_data, cert_len);
+}
+
+//======================================================================================================================
+// MARK: - Discovery Proxy Parameters Private Methods
+
+static mrc_discovery_proxy_parameters_t
+_mrc_discovery_proxy_parameters_create_or_copy(const mrc_discovery_proxy_parameters_t original)
+{
+	mrc_discovery_proxy_parameters_t params = NULL;
+	mrc_discovery_proxy_parameters_t obj = _mrc_discovery_proxy_parameters_new();
+	mdns_require_quiet(obj, exit);
+
+	if (original) {
+		obj->ifindex = original->ifindex;
+		obj->server_addresses = CFArrayCreateMutableCopy(kCFAllocatorDefault, 0, original->server_addresses);
+		mdns_require_quiet(obj->server_addresses, exit);
+
+		obj->domains = CFSetCreateMutableCopy(kCFAllocatorDefault, 0, original->domains);
+		mdns_require_quiet(obj->domains, exit);
+
+		obj->certs = xpc_copy(original->certs);
+		mdns_require_quiet(obj->certs, exit);
+	} else {
+		obj->server_addresses = CFArrayCreateMutable(kCFAllocatorDefault, 0, &mdns_cfarray_callbacks);
+		mdns_require_quiet(obj->server_addresses, exit);
+
+		obj->domains = CFSetCreateMutable(kCFAllocatorDefault, 0, &mdns_domain_name_cf_set_callbacks);
+		mdns_require_quiet(obj->domains, exit);
+
+		obj->certs = xpc_array_create_empty();
+		mdns_require_quiet(obj->certs, exit);
+	}
+	params = obj;
+	obj = NULL;
+
+exit:
+	mrc_forget(&obj);
+	return params;
+}
+
+//======================================================================================================================
+
+static char *
+_mrc_discovery_proxy_parameters_copy_description(const mrc_discovery_proxy_parameters_t me, const bool debug,
+	const bool privacy)
+{
+	char *description = NULL;
+	__block OSStatus err;
+	__block const char *prefix;
+	mdns_string_builder_t sb = mdns_string_builder_create(0, NULL);
+	mdns_require_quiet(sb, exit);
+
+	if (debug) {
+		const mdns_kind_t kind = mrc_get_kind(me);
+		err = mdns_string_builder_append_formatted(sb, "<%s: %p>: ", kind->name, (void *)me);
+		mdns_require_noerr_quiet(err, exit);
+	}
+	// Print interface index.
+	err = mdns_string_builder_append_formatted(sb, "interface index: %u, ", me->ifindex);
+	mdns_require_noerr_quiet(err, exit);
+
+	// Print server addresses.
+	err = mdns_string_builder_append_formatted(sb, "server addresses: {");
+	mdns_require_noerr_quiet(err, exit);
+
+	prefix = NULL;
+	const mdns_description_options_t desc_opts = privacy ? mdns_description_opt_privacy : mdns_description_opt_none;
+	mdns_cfarray_enumerate(me->server_addresses,
+	^ bool (const mdns_address_t address)
+	{
+		err = mdns_string_builder_append_description_with_prefix(sb, prefix, address, desc_opts);
+		prefix = ", ";
+		const bool proceed = !err;
+		return proceed;
+	});
+	mdns_require_noerr_quiet(err, exit);
+
+	err = mdns_string_builder_append_formatted(sb, "}");
+	mdns_require_noerr_quiet(err, exit);
+
+	// Print domains.
+	err = mdns_string_builder_append_formatted(sb, ", domains: {");
+	mdns_require_noerr_quiet(err, exit);
+
+	prefix = NULL;
+	mdns_cfset_enumerate(me->domains,
+	^ bool (const mdns_domain_name_t domain)
+	{
+		err = mdns_string_builder_append_description_with_prefix(sb, prefix, domain, desc_opts);
+		prefix = ", ";
+		const bool proceed = !err;
+		return proceed;
+	});
+	mdns_require_noerr_quiet(err, exit);
+
+	err = mdns_string_builder_append_formatted(sb, "}");
+	mdns_require_noerr_quiet(err, exit);
+
+	// Print certificate summary.
+	err = mdns_string_builder_append_formatted(sb, ", certificate count: %zu", xpc_array_get_count(me->certs));
+	mdns_require_noerr_quiet(err, exit);
+
+	description = mdns_string_builder_copy_string(sb);
+	mdns_require_quiet(description, exit);
+
+exit:
+	mdns_forget(&sb);
+	return description;
+}
+
+//======================================================================================================================
+
+static void
+_mrc_discovery_proxy_parameters_finalize(const mrc_discovery_proxy_parameters_t me)
+{
+	CFForget(&me->server_addresses);
+	CFForget(&me->domains);
+	xpc_forget(&me->certs);
+}
+
+//======================================================================================================================
+
+static xpc_object_t
+_mrc_discovery_proxy_parameters_create_xpc_dictionary(const mrc_discovery_proxy_parameters_t me)
+{
+	xpc_object_t result = NULL;
+	xpc_object_t dict = xpc_dictionary_create_empty();
+	mdns_require_quiet(dict, exit);
+
+	// Set interface index.
+	mrc_xpc_discovery_proxy_params_set_interface(dict, me->ifindex);
+
+	// Add server addresses.
+	bool completed = mdns_cfarray_enumerate(me->server_addresses,
+	^ bool (const mdns_address_t address)
+	{
+		bool proceed = false;
+		char *address_str = mdns_copy_description(address);
+		if (address_str) {
+			mrc_xpc_discovery_proxy_params_add_server_address(dict, address_str);
+			ForgetMem(&address_str);
+			proceed = true;
+		}
+		return proceed;
+	});
+	mdns_require_quiet(completed, exit);
+
+	// Add match domains.
+	mdns_cfset_enumerate(me->domains,
+	^ bool (const mdns_domain_name_t domain)
+	{
+		const char *domain_str = mdns_domain_name_get_presentation(domain);
+		mrc_xpc_discovery_proxy_params_add_match_domain(dict, domain_str);
+		return true;
+	});
+
+	// Add server certificates.
+	const size_t cert_count = xpc_array_get_count(me->certs);
+	for (size_t i = 0; i < cert_count; ++i) {
+		size_t cert_len = 0;
+		const uint8_t * const cert_data = xpc_array_get_data(me->certs, i, &cert_len);
+		if (cert_data && (cert_len > 0)) {
+			mrc_xpc_discovery_proxy_params_add_server_certificate(dict, cert_data, cert_len);
+		}
+	}
+	result = dict;
+	dict = NULL;
+
+exit:
+	xpc_forget(&dict);
+	return result;
+}
+
+//======================================================================================================================
+// MARK: - Cached Local Records Inquiry Public Methods
+
+mrc_cached_local_records_inquiry_t
+mrc_cached_local_records_inquiry_create(void)
+{
+	return _mrc_cached_local_records_inquiry_new();
+}
+
+//======================================================================================================================
+
+void
+mrc_cached_local_records_inquiry_set_queue(const mrc_cached_local_records_inquiry_t me, const dispatch_queue_t queue)
+{
+	_mrc_client_set_queue(me, queue);
+}
+
+//======================================================================================================================
+
+void
+mrc_cached_local_records_inquiry_set_result_handler(const mrc_cached_local_records_inquiry_t me,
+	const mrc_cached_local_records_inquiry_result_handler_t handler)
+{
+	mdns_require_return(!_mrc_client_is_immutable(me));
+	const mrc_cached_local_records_inquiry_result_handler_t new_handler = handler ? Block_copy(handler) : NULL;
+	BlockForget(&me->handler);
+	me->handler = new_handler;
+}
+
+//======================================================================================================================
+
+void
+mrc_cached_local_records_inquiry_activate(const mrc_cached_local_records_inquiry_t me)
+{
+	_mrc_client_activate_async(me);
+}
+
+//======================================================================================================================
+
+void
+mrc_cached_local_records_inquiry_invalidate(const mrc_cached_local_records_inquiry_t me)
+{
+	_mrc_client_invalidate_async(me, kNoErr);
+}
+
+//======================================================================================================================
+// MARK: - Cached Local Records Inquiry Private Methods
+
+static char *
+_mrc_cached_local_records_inquiry_copy_description(const mrc_cached_local_records_inquiry_t me, const bool debug,
+	__unused const bool privacy)
+{
+	char *description = NULL;
+	mdns_string_builder_t sb = mdns_string_builder_create(0, NULL);
+	mdns_require_quiet(sb, exit);
+
+	OSStatus err;
+	if (debug) {
+		const mdns_kind_t kind = mrc_get_kind(me);
+		err = mdns_string_builder_append_formatted(sb, "<%s: %p>: ", kind->name, (void *)me);
+		mdns_require_noerr_quiet(err, exit);
+	}
+	description = mdns_string_builder_copy_string(sb);
+	mdns_require_quiet(description, exit);
+
+exit:
+	mdns_forget(&sb);
+	return description;
+}
+
+//======================================================================================================================
+
+static void
+_mrc_cached_local_records_inquiry_finalize(__unused const mrc_cached_local_records_inquiry_t me)
+{
+	// Nothing to do for now.
+}
+
+//======================================================================================================================
+
+static xpc_object_t
+_mrc_cached_local_records_inquiry_create_start_message(__unused const mrc_cached_local_records_inquiry_t me,
+	const uint64_t cmd_id)
+{
+	return mrc_xpc_create_cached_local_record_inquiry_command_message(cmd_id);
+}
+
+//======================================================================================================================
+
+static char *
+_mrc_cached_local_records_inquiry_create_cleansed_string(const uint8_t * const string, const size_t string_len,
+	OSStatus * const out_error)
+{
+	OSStatus err;
+	char *result = NULL;
+	mdns_string_builder_t sb = mdns_string_builder_create(0, NULL);
+	mdns_require_action_quiet(sb, exit, err = kNoResourcesErr);
+
+	const uint8_t * ptr = string;
+	const uint8_t * const end = &string[string_len];
+	while (ptr < end) {
+		const uint8_t c = *ptr;
+		size_t processed_byte_count = 0;
+		const size_t remaining_len = (size_t)(end - ptr);
+		if (mdns_us_ascii(c)) {
+			if (mdns_us_ascii_isprint(c)) {
+				// Put printable ASCII characters in the destination string. But in the case of the backslash
+				// character, which is used as an escape character, escape it with a backslash.
+				if (c == '\\') {
+					err = mdns_string_builder_append_formatted(sb, "\\");
+					mdns_require_noerr_quiet(err, exit);
+				}
+				err = mdns_string_builder_append_formatted(sb, "%c", c);
+				mdns_require_noerr_quiet(err, exit);
+
+				processed_byte_count = 1;
+			}
+		} else {
+			// If this non-ASCII character is the start of a valid UTF-8 sequence, then simply put it in the
+			// destination string.
+			const size_t utf8_char_len = mdns_utf8_length_of_first_character(ptr, remaining_len);
+			if (utf8_char_len > 0) {
+				err = mdns_string_builder_append_formatted(sb, "%.*s", (int)utf8_char_len, ptr);
+				mdns_require_noerr_quiet(err, exit);
+
+				processed_byte_count = utf8_char_len;
+			}
+		}
+		// All other bytes, i.e., non-printable ASCII characters and those that aren't part of a valid UTF-8 byte
+		// sequence, are written as the \xHH escape sequence, where HH is the hex value of the byte encoded as a
+		// pair of ASCII hex digits.
+		if (processed_byte_count == 0) {
+			err = mdns_string_builder_append_formatted(sb, "\\x%02X", c);
+			mdns_require_noerr_quiet(err, exit);
+
+			processed_byte_count = 1;
+		}
+		ptr += Min(processed_byte_count, remaining_len);
+	}
+	result = mdns_string_builder_copy_string(sb);
+	mdns_require_action_quiet(result, exit, err = kNoMemoryErr);
+
+	err = kNoErr;
+
+exit:
+	mdns_assign(out_error, err);
+	mdns_forget(&sb);
+	return result;
+}
+
+//======================================================================================================================
+
+static void
+_mrc_cached_local_records_inquiry_enhance_record_info_dictionary(const xpc_object_t dict)
+{
+	char *first_label_str = NULL;
+	const char * const name_str = xpc_dictionary_get_string(dict, mrc_cached_local_record_key_name);
+	mdns_require_quiet(name_str, exit);
+
+	uint8_t name[kDomainNameLengthMax];
+	OSStatus err = DomainNameFromString(name, name_str, NULL);
+	mdns_require_noerr_quiet(err, exit);
+
+	const size_t first_label_len = name[0];
+	mdns_require_quiet(first_label_len > 0, exit);
+
+	const uint8_t * const first_label_data = &name[1];
+	first_label_str = _mrc_cached_local_records_inquiry_create_cleansed_string(first_label_data, first_label_len, &err);
+	mdns_require_action_quiet(first_label_str, exit, os_log_fault(_mrc_client_log(),
+		"Failed to convert first label to UTF-8 string: %{mdns:err}ld", (long)err));
+
+	xpc_dictionary_set_string(dict, mrc_cached_local_record_key_first_label, first_label_str);
+
+exit:
+	ForgetMem(&first_label_str);
+}
+
+//======================================================================================================================
+
+static xpc_object_t
+_mrc_cached_local_records_inquiry_process_create_enhanced_record_info_copy(const xpc_object_t record_info)
+{
+	xpc_object_t enhanced_record_info = xpc_copy(record_info);
+	mdns_require_quiet(enhanced_record_info, exit);
+
+	xpc_array_apply(enhanced_record_info,
+	^ bool (__unused const size_t index, const xpc_object_t dict)
+	{
+		if (xpc_get_type(dict) == XPC_TYPE_DICTIONARY) {
+			_mrc_cached_local_records_inquiry_enhance_record_info_dictionary(dict);
+		}
+		return true;
+	});
+
+exit:
+	return enhanced_record_info;
+}
+
+//======================================================================================================================
+
+static void
+_mrc_cached_local_records_inquiry_invoke_user_handler(const mrc_cached_local_records_inquiry_t me,
+	const xpc_object_t record_info, const OSStatus error)
+{
+	const dispatch_queue_t user_queue = _mrc_client_get_user_queue(me);
+	if (user_queue && me->handler) {
+		xpc_object_t record_info_copy = NULL;
+		if (record_info) {
+			record_info_copy = _mrc_cached_local_records_inquiry_process_create_enhanced_record_info_copy(record_info);
+			if (!record_info_copy) {
+				record_info_copy = record_info;
+				xpc_retain(record_info_copy);
+			}
+		}
+		const mrc_cached_local_records_inquiry_result_handler_t handler = me->handler;
+		dispatch_async(user_queue,
+		^{
+			handler(record_info_copy, error);
+			xpc_object_t tmp = record_info_copy;
+			xpc_forget(&tmp);
+		});
+	}
+	BlockForget(&me->handler);
+}
+
+//======================================================================================================================
+
+static void
+_mrc_cached_local_records_inquiry_invoke_user_handler_with_record_info(const mrc_cached_local_records_inquiry_t me,
+	const xpc_object_t record_info)
+{
+	_mrc_cached_local_records_inquiry_invoke_user_handler(me, record_info, kNoErr);
+}
+
+//======================================================================================================================
+
+static void
+_mrc_cached_local_records_inquiry_invoke_user_handler_with_error(const mrc_cached_local_records_inquiry_t me,
+	const OSStatus error)
+{
+	_mrc_cached_local_records_inquiry_invoke_user_handler(me, NULL, error);
+}
+
+//======================================================================================================================
+
+static void
+_mrc_cached_local_records_inquiry_handle_start(const mrc_cached_local_records_inquiry_t me, const xpc_object_t result)
+{
+	bool valid_result = false;
+	xpc_object_t record_info = NULL;
+	if (result) {
+		record_info = mrc_xpc_cached_local_record_inquiry_result_get_record_info(result, &valid_result);
+	}
+	if (valid_result) {
+		_mrc_cached_local_records_inquiry_invoke_user_handler_with_record_info(me, record_info);
+	} else {
+		_mrc_cached_local_records_inquiry_invoke_user_handler_with_error(me, kResponseErr);
+	}
+}
+
+//======================================================================================================================
+
+static void
+_mrc_cached_local_records_inquiry_handle_invalidation(const mrc_cached_local_records_inquiry_t me, const OSStatus error)
+{
+	_mrc_cached_local_records_inquiry_invoke_user_handler_with_error(me, error);
+}
+
+//======================================================================================================================
+// MARK: - Record Cache Flush Public Methods
+
+mrc_record_cache_flush_t
+mrc_record_cache_flush_create(void)
+{
+	return _mrc_record_cache_flush_new();
+}
+
+//======================================================================================================================
+
+void
+mrc_record_cache_flush_set_queue(const mrc_record_cache_flush_t me, const dispatch_queue_t queue)
+{
+	_mrc_client_set_queue(me, queue);
+}
+
+//======================================================================================================================
+
+void
+mrc_record_cache_flush_set_record_name(const mrc_record_cache_flush_t me, const mdns_domain_name_t record_name)
+{
+	mdns_require_return(!_mrc_client_is_immutable(me));
+	mdns_replace(&me->record_name, record_name);
+}
+
+//======================================================================================================================
+
+void
+mrc_record_cache_flush_set_key_tag(const mrc_record_cache_flush_t me, const uint16_t key_tag)
+{
+	mdns_require_return(!_mrc_client_is_immutable(me));
+	me->key_tag = key_tag;
+	me->have_key_tag = true;
+}
+
+//======================================================================================================================
+
+void
+mrc_record_cache_flush_set_result_handler(const mrc_record_cache_flush_t me,
+	const mrc_record_cache_flush_result_handler_t handler)
+{
+	mdns_require_return(!_mrc_client_is_immutable(me));
+	const mrc_record_cache_flush_result_handler_t new_handler = handler ? Block_copy(handler) : NULL;
+	BlockForget(&me->handler);
+	me->handler = new_handler;
+}
+
+//======================================================================================================================
+
+void
+mrc_record_cache_flush_activate(const mrc_record_cache_flush_t me)
+{
+	_mrc_client_activate_async(me);
+}
+
+//======================================================================================================================
+
+void
+mrc_record_cache_flush_invalidate(const mrc_record_cache_flush_t me)
+{
+	_mrc_client_invalidate_async(me, kNoErr);
+}
+
+//======================================================================================================================
+// MARK: - Record Cache Flush Private Methods
+
+static char *
+_mrc_record_cache_flush_copy_description(const mrc_record_cache_flush_t me, const bool debug, const bool privacy)
+{
+	char *description = NULL;
+	mdns_string_builder_t sb = mdns_string_builder_create(0, NULL);
+	mdns_require_quiet(sb, exit);
+
+	OSStatus err;
+	if (debug) {
+		const mdns_kind_t kind = mrc_get_kind(me);
+		err = mdns_string_builder_append_formatted(sb, "<%s: %p>: ", kind->name, (void *)me);
+		mdns_require_noerr_quiet(err, exit);
+	}
+	err = mdns_string_builder_append_formatted(sb, "record name: ");
+	mdns_require_noerr_quiet(err, exit);
+
+	if (me->record_name) {
+		const mdns_description_options_t desc_opts = privacy ? mdns_description_opt_privacy : mdns_description_opt_none;
+		err = mdns_string_builder_append_description(sb, me->record_name, desc_opts);
+		mdns_require_noerr_quiet(err, exit);
+	} else {
+		err = mdns_string_builder_append_formatted(sb, "«NO NAME»");
+		mdns_require_noerr_quiet(err, exit);
+	}
+	if (me->have_key_tag) {
+		err = mdns_string_builder_append_formatted(sb, ", key tag: %u", me->key_tag);
+		mdns_require_noerr_quiet(err, exit);
+	}
+	description = mdns_string_builder_copy_string(sb);
+	mdns_require_quiet(description, exit);
+
+exit:
+	mdns_forget(&sb);
+	return description;
+}
+
+//======================================================================================================================
+
+static void
+_mrc_record_cache_flush_finalize(const mrc_record_cache_flush_t me)
+{
+	mdns_forget(&me->record_name);
+}
+
+//======================================================================================================================
+
+static xpc_object_t
+_mrc_record_cache_flush_create_start_message(const mrc_record_cache_flush_t me, const uint64_t cmd_id)
+{
+	xpc_object_t params = xpc_dictionary_create_empty();
+	if (me->record_name) {
+		mrc_xpc_record_cache_flush_params_set_record_name(params, mdns_domain_name_get_presentation(me->record_name));
+	}
+	if (me->have_key_tag) {
+		mrc_xpc_record_cache_flush_params_set_key_tag(params, me->key_tag);
+	}
+	const xpc_object_t msg = mrc_xpc_create_record_cache_flush_command_message(cmd_id, params);
+	xpc_forget(&params);
+	return msg;
+}
+
+//======================================================================================================================
+
+static void
+_mrc_record_cache_flush_invoke_user_handler(const mrc_record_cache_flush_t me,
+	const mrc_record_cache_flush_result_t result, const OSStatus error)
+{
+	const dispatch_queue_t user_queue = _mrc_client_get_user_queue(me);
+	if (user_queue && me->handler) {
+		const mrc_record_cache_flush_result_handler_t handler = me->handler;
+		dispatch_async(user_queue,
+		^{
+			handler(result, error);
+		});
+	}
+	BlockForget(&me->handler);
+}
+
+//======================================================================================================================
+
+static void
+_mrc_record_cache_flush_handle_start(const mrc_record_cache_flush_t me, __unused const xpc_object_t result)
+{
+	_mrc_record_cache_flush_invoke_user_handler(me, mrc_record_cache_flush_result_complete, kNoErr);
+}
+
+//======================================================================================================================
+
+static void
+_mrc_record_cache_flush_handle_invalidation(const mrc_record_cache_flush_t me, const OSStatus error)
+{
+	_mrc_record_cache_flush_invoke_user_handler(me, mrc_record_cache_flush_result_incomplete, error);
+}
+
+//======================================================================================================================
 // MARK: - Internal Functions
 
+static uint64_t
+_mrc_client_get_new_command_id(void)
+{
+	static uint64_t last_command_id = 0;
+	return ++last_command_id;
+}
+
+//======================================================================================================================
+
 static os_log_t
 _mrc_client_log(void)
 {
@@ -930,22 +2561,8 @@
 static void
 _mrc_client_handle_connection_interruption(void)
 {
-	for (mrc_dns_proxy_t proxy = g_dns_proxy_list; proxy; proxy = proxy->next) {
-		switch (proxy->state) {
-			case mrc_dns_proxy_state_starting:
-			case mrc_dns_proxy_state_started:
-				if (proxy->state == mrc_dns_proxy_state_started) {
-					_mrc_dns_proxy_generate_event(proxy, mrc_dns_proxy_event_interruption);
-				}
-				proxy->cmd_id = _mrc_client_get_new_command_id();
-				_mrc_dns_proxy_start(proxy);
-				break;
-
-			case mrc_dns_proxy_state_failed:
-			case mrc_dns_proxy_state_invalidated:
-			case mrc_dns_proxy_state_nascent:
-				break;
-		}
+	for (mrc_session_t session = g_session_list; session; session = session->next) {
+		_mrc_session_handle_connection_interruption(session);
 	}
 	for (mrc_dns_proxy_state_inquiry_t inquiry = g_dns_proxy_state_inquiry_list; inquiry; inquiry = inquiry->next) {
 		switch (inquiry->state) {
@@ -956,6 +2573,7 @@
 			case mrc_dns_proxy_state_inquiry_state_nascent:
 			case mrc_dns_proxy_state_inquiry_state_registered:
 			case mrc_dns_proxy_state_inquiry_state_done:
+			MDNS_COVERED_SWITCH_DEFAULT:
 				break;
 		}
 	}
@@ -963,6 +2581,28 @@
 
 //======================================================================================================================
 
+static void
+_mrc_client_connection_handle_notification(const xpc_object_t notification)
+{
+	const uint64_t cmd_id = mrc_xpc_notification_get_id(notification);
+	mrc_session_t session;
+	for (session = g_session_list; session; session = session->next) {
+		if (session->cmd_id == cmd_id) {
+			break;
+		}
+	}
+	if (!session) {
+		os_log_fault(_mrc_client_log(), "Unrecognized notification ID: %" PRIu64, cmd_id);
+		goto exit;
+	}
+	_mrc_session_handle_notification(session, notification);
+
+exit:
+	return;
+}
+
+//======================================================================================================================
+
 static xpc_connection_t
 _mrc_client_connection(void)
 {
@@ -975,7 +2615,9 @@
 	^(const xpc_object_t event)
 	{
 		const xpc_type_t type = xpc_get_type(event);
-		if (type == XPC_TYPE_ERROR) {
+		if (type == XPC_TYPE_DICTIONARY) {
+			_mrc_client_connection_handle_notification(event);
+		} else if (type == XPC_TYPE_ERROR) {
 			os_log_error(_mrc_client_log(),
 				"Connection error: %{public}s", xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION));
 		} else {
diff --git a/mDNSMacOSX/libmrc/src/mrc_cached_local_record_keys.h b/mDNSMacOSX/libmrc/src/mrc_cached_local_record_keys.h
new file mode 100644
index 0000000..680f282
--- /dev/null
+++ b/mDNSMacOSX/libmrc/src/mrc_cached_local_record_keys.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MRC_CACHED_LOCAL_RECORD_KEY_H
+#define MRC_CACHED_LOCAL_RECORD_KEY_H
+
+/*!
+ *	@brief
+ *		First label of the record name as a string with minimal backslash escape sequences.
+ */
+#define MRC_CACHED_LOCAL_RECORD_KEY_FIRST_LABEL	"first_label"
+
+/*!
+ *	@brief
+ *		Record name as a string.
+ */
+#define MRC_CACHED_LOCAL_RECORD_KEY_NAME	"name"
+
+/*!
+ *	@brief
+ *		Record RDATA's text representation as a string.
+ */
+#define MRC_CACHED_LOCAL_RECORD_KEY_RDATA	"rdata"
+
+/*!
+ *	@brief
+ *		Record's source IPv4 or IPv6 address's text representation as a string.
+ */
+#define MRC_CACHED_LOCAL_RECORD_KEY_SOURCE_ADDRESS	"source_address"
+
+#endif	// MRC_CACHED_LOCAL_RECORD_KEY_H
diff --git a/mDNSMacOSX/libmrc/src/mrc_internal.h b/mDNSMacOSX/libmrc/src/mrc_internal.h
new file mode 100644
index 0000000..a5431c5
--- /dev/null
+++ b/mDNSMacOSX/libmrc/src/mrc_internal.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MRC_INTERNAL_H
+#define MRC_INTERNAL_H
+
+MRC_DECL(client);
+MRC_DECL(session);
+
+#endif	// MRC_INTERNAL_H
diff --git a/mDNSMacOSX/libmrc/src/mrc_object.c b/mDNSMacOSX/libmrc/src/mrc_object.c
index ea9f24a..9722001 100644
--- a/mDNSMacOSX/libmrc/src/mrc_object.c
+++ b/mDNSMacOSX/libmrc/src/mrc_object.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 Apple Inc. All rights reserved.
+ * Copyright (c) 2021-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,11 +17,20 @@
 #include <mrc/object.h>
 
 #include "mdns_obj.h"
+#include "mrc_object_internal.h"
 #include "mdns_strict.h"
 
 //======================================================================================================================
 // MARK: - Object Public Methods
 
+mdns_kind_t
+mrc_get_kind(const mrc_object_t me)
+{
+	return mdns_obj_get_kind(me);
+}
+
+//======================================================================================================================
+
 void
 mrc_retain(const mrc_object_t me)
 {
diff --git a/mDNSMacOSX/libmrc/src/mrc_object_internal.h b/mDNSMacOSX/libmrc/src/mrc_object_internal.h
new file mode 100644
index 0000000..8c79eb9
--- /dev/null
+++ b/mDNSMacOSX/libmrc/src/mrc_object_internal.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MRC_OBJECT_INTERNAL_H
+#define MRC_OBJECT_INTERNAL_H
+
+#include <mdns/base.h>
+#include <mdns/object.h>
+
+#include "mdns_obj.h"
+
+MDNS_ASSUME_NONNULL_BEGIN
+
+__BEGIN_DECLS
+
+/*!
+ *	@brief
+ *		Gets a reference to an object's kind.
+ *
+ *	@param object
+ *		The object.
+ *
+ *	@discussion
+ *		This function is meant only for object implementers.
+ */
+mdns_kind_t
+mrc_get_kind(mrc_any_t object);
+
+__END_DECLS
+
+MDNS_ASSUME_NONNULL_END
+
+#endif	// MRC_OBJECT_INTERNAL_H
diff --git a/mDNSMacOSX/libmrc/src/mrc_object_members_internal.h b/mDNSMacOSX/libmrc/src/mrc_object_members_internal.h
new file mode 100644
index 0000000..8a8c040
--- /dev/null
+++ b/mDNSMacOSX/libmrc/src/mrc_object_members_internal.h
@@ -0,0 +1,23 @@
+/*
+ * Copyright (c) 2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MRC_OBJECT_MEMBERS_INTERNAL_H
+#define MRC_OBJECT_MEMBERS_INTERNAL_H
+
+#define MRC_OBJECT_MEMBERS_INTERNAL	\
+	MRC_UNION_MEMBER(client);
+
+#endif	// MRC_OBJECT_MEMBERS_INTERNAL_H
diff --git a/mDNSMacOSX/libmrc/src/mrc_objects.h b/mDNSMacOSX/libmrc/src/mrc_objects.h
index 921b989..ecccc07 100644
--- a/mDNSMacOSX/libmrc/src/mrc_objects.h
+++ b/mDNSMacOSX/libmrc/src/mrc_objects.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 Apple Inc. All rights reserved.
+ * Copyright (c) 2021-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -19,9 +19,13 @@
 
 #include "mdns_objects.h"
 
-#define MRC_OBJECT_SUBKIND_DEFINE_ABSTRACT(NAME)	MDNS_OBJ_SUBKIND_DEFINE_ABSTRACT(mrc_ ## NAME)
-#define MRC_OBJECT_SUBKIND_DEFINE(NAME)				MDNS_OBJ_SUBKIND_DEFINE(mrc_ ## NAME)
-#define MRC_OBJECT_SUBKIND_DEFINE_FULL(NAME)		MDNS_OBJ_SUBKIND_DEFINE_FULL(mrc_ ## NAME)
+#define MRC_OBJECT_SUBKIND_DEFINE_ABSTRACT(NAME)			MDNS_OBJ_SUBKIND_DEFINE_ABSTRACT(mrc_ ## NAME)
+#define MRC_OBJECT_SUBKIND_DEFINE(NAME)						MDNS_OBJ_SUBKIND_DEFINE(mrc_ ## NAME)
+#define MRC_OBJECT_SUBKIND_DEFINE_FULL(NAME)				MDNS_OBJ_SUBKIND_DEFINE_FULL(mrc_ ## NAME)
+#define MRC_OBJECT_SUBKIND_DEFINE_ABSTRACT_MINIMAL_WITHOUT_ALLOC(NAME) \
+	MDNS_OBJ_SUBKIND_DEFINE_ABSTRACT_MINIMAL_WITHOUT_ALLOC(mrc_ ## NAME)
+#define MRC_OBJECT_SUBKIND_DEFINE_ALLOC(NAME)				MDNS_OBJ_SUBKIND_DEFINE_ALLOC(mrc_ ## NAME)
+#define MRC_OBJECT_SUBKIND_DEFINE_NEW_WITH_KIND(NAME, KIND)	MDNS_OBJ_SUBKIND_DEFINE_NEW_WITH_KIND(mrc_ ## NAME, KIND)
 
 #define MRC_BASE_CHECK(NAME, SUPER)	MDNS_OBJ_BASE_CHECK(mrc_ ## NAME, mrc_ ## SUPER)
 
diff --git a/mDNSMacOSX/libmrc/src/mrc_objects.m b/mDNSMacOSX/libmrc/src/mrc_objects.m
index 075bd75..ff348aa 100644
--- a/mDNSMacOSX/libmrc/src/mrc_objects.m
+++ b/mDNSMacOSX/libmrc/src/mrc_objects.m
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021-2022 Apple Inc. All rights reserved.
+ * Copyright (c) 2021-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-#import <mrc/dns_proxy.h>
-#import <mrc/object.h>
+#import <mrc/private.h>
 
 #import "mdns_objc_support.h"
+#import "mrc_internal.h"
 #import "mdns_strict.h"
 
 //======================================================================================================================
@@ -37,6 +37,22 @@
 
 MRC_OBJC_BASE_CLASS_IMPLEMENTATION(object);
 
+MRC_OBJC_CLASS_IMPLEMENTATION(cached_local_records_inquiry);
+MRC_OBJC_CLASS_IMPLEMENTATION(discovery_proxy);
+MRC_OBJC_CLASS_IMPLEMENTATION(discovery_proxy_parameters);
 MRC_OBJC_CLASS_IMPLEMENTATION(dns_proxy);
 MRC_OBJC_CLASS_IMPLEMENTATION(dns_proxy_parameters);
 MRC_OBJC_CLASS_IMPLEMENTATION(dns_proxy_state_inquiry);
+MRC_OBJC_CLASS_IMPLEMENTATION(dns_service_registration);
+MRC_OBJC_CLASS_IMPLEMENTATION(record_cache_flush);
+MRC_OBJC_CLASS_IMPLEMENTATION(session);
+
+// Workaround until libmrc.dylib can link libmds.dylib without a B&I circular dependency.
+#define MRC_MDNS_WORKAROUND(NAME)					\
+	MRC_DECL(mdns_ ## NAME);						\
+	MRC_OBJC_CLASS_IMPLEMENTATION(mdns_ ## NAME);	\
+	const mdns_class_t MDNS_OBJ_CLASS(mdns_ ## NAME) = MDNS_OBJ_CLASS(mrc_mdns_ ## NAME)
+
+MRC_MDNS_WORKAROUND(string_builder);
+MRC_MDNS_WORKAROUND(domain_name);
+MRC_MDNS_WORKAROUND(address);
diff --git a/mDNSMacOSX/libmrc/src/mrc_xpc.c b/mDNSMacOSX/libmrc/src/mrc_xpc.c
index 87be70c..34b255d 100644
--- a/mDNSMacOSX/libmrc/src/mrc_xpc.c
+++ b/mDNSMacOSX/libmrc/src/mrc_xpc.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2021-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,10 +16,12 @@
 
 #include "mrc_xpc.h"
 
-#include <mdns/xpc.h>
 #include "memory.h"
+#include "mrc_cached_local_record_keys.h"
 
 #include <CoreUtils/CoreUtils.h>
+#include <mdns/dns_service.h>
+#include <mdns/xpc.h>
 #include "mdns_strict.h"
 
 //======================================================================================================================
@@ -49,15 +51,9 @@
 const char * const g_mrc_mach_service_name = "com.apple.mDNSResponder.control";
 
 //======================================================================================================================
-// MARK: - Commands
-
-const char * const g_mrc_command_dns_proxy_start		= "dns_proxy.start";
-const char * const g_mrc_command_dns_proxy_stop			= "dns_proxy.stop";
-const char * const g_mrc_command_dns_proxy_get_state	= "dns_proxy.get_state";
-
-//======================================================================================================================
 // MARK: - Top-Level Dictionary Keys
 
+static const char * const g_mrc_message_key_body	= "body";
 static const char * const g_mrc_message_key_command	= "command";
 static const char * const g_mrc_message_key_error	= "error";
 static const char * const g_mrc_message_key_id		= "id";
@@ -65,8 +61,19 @@
 static const char * const g_mrc_message_key_result	= "result";
 
 //======================================================================================================================
-// MARK: - DNS Proxy Keys
+// MARK: - Result Keys
 
+static const char * const g_mrc_result_key_description = "description";
+
+//======================================================================================================================
+// MARK: - DNS Proxy Commands and Keys
+
+// Commands
+const char * const g_mrc_command_dns_proxy_start		= "dns_proxy.start";
+const char * const g_mrc_command_dns_proxy_stop			= "dns_proxy.stop";
+const char * const g_mrc_command_dns_proxy_get_state	= "dns_proxy.get_state";
+
+// Keys
 static const char * const g_mrc_dns_proxy_key_input_interfaces		= "input_interfaces";
 static const char * const g_mrc_dns_proxy_key_nat64_prefix_bit_len	= "nat64_prefix.bit_len";
 static const char * const g_mrc_dns_proxy_key_nat64_prefix_bits		= "nat64_prefix.bits";
@@ -74,9 +81,56 @@
 static const char * const g_mrc_dns_proxy_key_force_aaaa_synthesis	= "force_aaaa_synth";
 
 //======================================================================================================================
-// MARK: - Result Keys
+// MARK: - DNS Service Registration Commands and Keys
 
-static const char * const g_mrc_result_key_description = "description";
+// Commands
+const char * const g_mrc_command_dns_service_registration_start	= "dns_service_registration.start";
+const char * const g_mrc_command_dns_service_registration_stop	= "dns_service_registration.stop";
+
+// Keys
+static const char * const g_mrc_dns_service_registration_key_definition					= "definition";
+static const char * const g_mrc_dns_service_registration_key_definition_type			= "definition_type";
+static const char * const g_mrc_dns_service_registration_key_reports_connection_errors	= "reports_connection_errors";
+
+// Notification key
+static const char * const g_mrc_dns_service_registration_notification_key_connection_error = "connection_error";
+
+//======================================================================================================================
+// MARK: - Discovery Proxy Commands and Keys
+
+// Commands
+const char * const g_mrc_command_discovery_proxy_start	= "discovery_proxy.start";
+const char * const g_mrc_command_discovery_proxy_stop	= "discovery_proxy.stop";
+
+// Keys
+static const char * const g_mrc_discovery_proxy_key_addresses			= "addresses";
+static const char * const g_mrc_discovery_proxy_key_interface			= "interface";
+static const char * const g_mrc_discovery_proxy_key_match_domains		= "match_domains";
+static const char * const g_mrc_discovery_proxy_key_server_certificates	= "server_certificates";
+
+//======================================================================================================================
+// MARK: - Cached Local Record Inquiry Commands and Keys
+
+// Commands
+const char * const g_mrc_command_cached_local_record_inquiry = "record_cache.local_record_inquiry";
+
+// Result Keys
+static const char * const g_mrc_cached_local_record_inquiry_result_key_record_info = "record_info";
+
+// Record Keys
+static const char * const g_mrc_cached_local_record_key_name			= MRC_CACHED_LOCAL_RECORD_KEY_NAME;
+static const char * const g_mrc_cached_local_record_key_rdata			= MRC_CACHED_LOCAL_RECORD_KEY_RDATA;
+static const char * const g_mrc_cached_local_record_key_source_address	= MRC_CACHED_LOCAL_RECORD_KEY_SOURCE_ADDRESS;
+
+//======================================================================================================================
+// MARK: - Record Cache Flush Commands and Keys
+
+// Commands
+const char * const g_mrc_command_record_cache_flush = "record_cache.flush";
+
+// Keys
+static const char * const g_mrc_record_cache_flush_record_name	= "record_name";
+static const char * const g_mrc_record_cache_flush_key_tag		= "key_tag";
 
 //======================================================================================================================
 // MARK: - External Message Functions
@@ -141,6 +195,33 @@
 }
 
 //======================================================================================================================
+
+xpc_object_t
+mrc_xpc_create_notification(const uint64_t ident, const xpc_object_t body)
+{
+	xpc_object_t _Nonnull notification = xpc_dictionary_create_empty();
+	_mrc_xpc_message_set_id(notification, ident);
+	xpc_dictionary_set_value(notification, g_mrc_message_key_body, body);
+	return notification;
+}
+
+//======================================================================================================================
+
+uint64_t
+mrc_xpc_notification_get_id(xpc_object_t notification)
+{
+	return xpc_dictionary_get_uint64(notification, g_mrc_message_key_id);
+}
+
+//======================================================================================================================
+
+xpc_object_t
+mrc_xpc_notification_get_body(const xpc_object_t notification)
+{
+	return xpc_dictionary_get_dictionary(notification, g_mrc_message_key_body);
+}
+
+//======================================================================================================================
 // MARK: - External DNS Proxy Functions
 
 xpc_object_t
@@ -317,6 +398,303 @@
 }
 
 //======================================================================================================================
+// MARK: - External DNS Service Registration Functions
+
+xpc_object_t
+mrc_xpc_create_dns_service_registration_start_command_message(const uint64_t ident, const xpc_object_t params)
+{
+	return _mrc_xpc_create_command_message(ident, g_mrc_command_dns_service_registration_start, params);
+}
+
+//======================================================================================================================
+
+xpc_object_t
+mrc_xpc_create_dns_service_registration_stop_command_message(const uint64_t ident)
+{
+	return _mrc_xpc_create_command_message(ident, g_mrc_command_dns_service_registration_stop, NULL);
+}
+
+//======================================================================================================================
+
+void
+mrc_xpc_dns_service_registration_params_set_defintion_dictionary(const xpc_object_t params,
+	const xpc_object_t dict)
+{
+	xpc_dictionary_set_value(params, g_mrc_dns_service_registration_key_definition, dict);
+}
+
+//======================================================================================================================
+
+mdns_xpc_dictionary_t
+mrc_xpc_dns_service_registration_params_get_defintion_dictionary(const xpc_object_t params)
+{
+	return mdns_xpc_dictionary_get_dictionary(params, g_mrc_dns_service_registration_key_definition);
+}
+
+//======================================================================================================================
+
+void
+mrc_xpc_dns_service_registration_params_set_definition_type(const xpc_object_t params,
+	const mrc_dns_service_definition_type_t type)
+{
+	mdns_xpc_dictionary_set_uint8(params, g_mrc_dns_service_registration_key_definition_type, type);
+}
+
+//======================================================================================================================
+
+mrc_dns_service_definition_type_t
+mrc_xpc_dns_service_registration_params_get_definition_type(const xpc_object_t params, bool * const out_valid)
+{
+	bool valid;
+	const mrc_dns_service_definition_type_t type_val = mdns_xpc_dictionary_get_uint8(params,
+		g_mrc_dns_service_registration_key_definition_type, &valid);
+	mdns_require_quiet(valid, exit);
+
+	switch (type_val) {
+		case mrc_dns_service_definition_type_do53:
+		case mrc_dns_service_definition_type_push:
+			valid = true;
+			break;
+
+		MDNS_COVERED_SWITCH_DEFAULT:
+			valid = false;
+			break;
+	}
+
+exit:
+	mdns_assign(out_valid, valid);
+	return type_val;
+}
+
+//======================================================================================================================
+
+void
+mrc_xpc_dns_service_registration_params_set_reports_connection_errors(const xpc_object_t params,
+	const bool reports_connection_errors)
+{
+	xpc_dictionary_set_bool(params, g_mrc_dns_service_registration_key_reports_connection_errors,
+		reports_connection_errors);
+}
+
+//======================================================================================================================
+
+bool
+mrc_xpc_dns_service_registration_params_get_reports_connection_errors(const xpc_object_t params)
+{
+	return mdns_xpc_dictionary_get_bool(params, g_mrc_dns_service_registration_key_reports_connection_errors,
+		NULL);
+}
+
+//======================================================================================================================
+
+xpc_object_t
+mrc_xpc_dns_service_registration_notification_create_connection_error_body(const OSStatus connection_error,
+	OSStatus * const out_error)
+{
+	OSStatus err;
+	mdns_xpc_dictionary_t notification = mdns_xpc_dictionary_create_empty();
+	mdns_require_action_quiet(notification, exit, err = kNoResourcesErr);
+
+	mdns_xpc_dictionary_set_int32(notification, g_mrc_dns_service_registration_notification_key_connection_error,
+		connection_error);
+
+	err = kNoErr;
+exit:
+	mdns_assign(out_error, err);
+	return notification;
+}
+
+//======================================================================================================================
+
+OSStatus
+mrc_xpc_dns_service_registration_notification_get_connection_error(const xpc_object_t notification_body,
+	bool * const out_valid)
+{
+	return (OSStatus)mdns_xpc_dictionary_get_int32(notification_body,
+		g_mrc_dns_service_registration_notification_key_connection_error, out_valid);
+}
+
+//======================================================================================================================
+// MARK: - External Discovery Proxy Functions
+
+uint32_t
+mrc_xpc_discovery_proxy_params_get_interface(const xpc_object_t params, bool * const out_valid)
+{
+	return mdns_xpc_dictionary_get_uint32(params, g_mrc_discovery_proxy_key_interface, out_valid);
+}
+
+//======================================================================================================================
+
+xpc_object_t
+mrc_xpc_discovery_proxy_params_get_ip_address_strings(const xpc_object_t params)
+{
+	return mdns_xpc_dictionary_get_array(params, g_mrc_discovery_proxy_key_addresses);
+}
+
+//======================================================================================================================
+
+xpc_object_t
+mrc_xpc_discovery_proxy_params_get_match_domains(const xpc_object_t params)
+{
+	return mdns_xpc_dictionary_get_array(params, g_mrc_discovery_proxy_key_match_domains);
+}
+
+//======================================================================================================================
+
+xpc_object_t
+mrc_xpc_discovery_proxy_params_get_server_certificates(const xpc_object_t params)
+{
+	return mdns_xpc_dictionary_get_array(params, g_mrc_discovery_proxy_key_server_certificates);
+}
+
+//======================================================================================================================
+
+xpc_object_t
+mrc_xpc_create_discovery_proxy_start_command_message(const uint64_t ident, const xpc_object_t params)
+{
+	return _mrc_xpc_create_command_message(ident, g_mrc_command_discovery_proxy_start, params);
+}
+
+//======================================================================================================================
+
+xpc_object_t
+mrc_xpc_create_discovery_proxy_stop_command_message(const uint64_t ident)
+{
+	return _mrc_xpc_create_command_message(ident, g_mrc_command_discovery_proxy_stop, NULL);
+}
+
+//======================================================================================================================
+
+void
+mrc_xpc_discovery_proxy_params_set_interface(const xpc_object_t params, const uint32_t ifindex)
+{
+	mdns_xpc_dictionary_set_uint32(params, g_mrc_discovery_proxy_key_interface, ifindex);
+}
+
+//======================================================================================================================
+
+void
+mrc_xpc_discovery_proxy_params_add_server_address(const xpc_object_t params, const char * const address)
+{
+	xpc_object_t server_addresses = mrc_xpc_discovery_proxy_params_get_ip_address_strings(params);
+	if (!server_addresses) {
+		xpc_object_t new_server_addresses = xpc_array_create_empty();
+		xpc_dictionary_set_value(params, g_mrc_discovery_proxy_key_addresses, new_server_addresses);
+		server_addresses = new_server_addresses;
+		xpc_forget(&new_server_addresses);
+	}
+	mdns_xpc_array_append_string(server_addresses, address);
+}
+
+//======================================================================================================================
+
+void
+mrc_xpc_discovery_proxy_params_add_match_domain(const xpc_object_t params, const char * const domain)
+{
+	xpc_object_t domains = mrc_xpc_discovery_proxy_params_get_match_domains(params);
+	if (!domains) {
+		xpc_object_t new_domains = xpc_array_create_empty();
+		xpc_dictionary_set_value(params, g_mrc_discovery_proxy_key_match_domains, new_domains);
+		domains = new_domains;
+		xpc_forget(&new_domains);
+	}
+	mdns_xpc_array_append_string(domains, domain);
+}
+
+//======================================================================================================================
+
+void
+mrc_xpc_discovery_proxy_params_add_server_certificate(const xpc_object_t params, const uint8_t * const cert_data,
+	const size_t cert_len)
+{
+	xpc_object_t certs = mrc_xpc_discovery_proxy_params_get_server_certificates(params);
+	if (!certs) {
+		xpc_object_t new_certs = xpc_array_create_empty();
+		xpc_dictionary_set_value(params, g_mrc_discovery_proxy_key_server_certificates, new_certs);
+		certs = new_certs;
+		xpc_forget(&new_certs);
+	}
+	mdns_xpc_array_append_data(certs, cert_data, cert_len);
+}
+
+//======================================================================================================================
+// MARK: - External Cached Local Record Inquiry Functions
+
+xpc_object_t
+mrc_xpc_create_cached_local_record_inquiry_command_message(const uint64_t ident)
+{
+	return _mrc_xpc_create_command_message(ident, g_mrc_command_cached_local_record_inquiry, NULL);
+}
+
+//======================================================================================================================
+
+void
+mrc_xpc_cached_local_record_inquiry_result_add(const xpc_object_t result, const char * const name,
+	const char * const rdata, const char * const source_address)
+{
+	xpc_object_t dict = xpc_dictionary_create_empty();
+	xpc_dictionary_set_string(dict, g_mrc_cached_local_record_key_name, name);
+	if (rdata) {
+		xpc_dictionary_set_string(dict, g_mrc_cached_local_record_key_rdata, rdata);
+	}
+	if (source_address) {
+		xpc_dictionary_set_string(dict, g_mrc_cached_local_record_key_source_address, source_address);
+	}
+	mdns_xpc_dictionary_array_append_value(result, g_mrc_cached_local_record_inquiry_result_key_record_info, dict);
+	xpc_forget(&dict);
+}
+
+//======================================================================================================================
+
+xpc_object_t
+mrc_xpc_cached_local_record_inquiry_result_get_record_info(const xpc_object_t result, bool * const out_valid)
+{
+	return mdns_xpc_dictionary_get_optional_array(result, g_mrc_cached_local_record_inquiry_result_key_record_info,
+		out_valid);
+}
+
+//======================================================================================================================
+// MARK: - External Record Cache Flush Functions
+
+xpc_object_t
+mrc_xpc_create_record_cache_flush_command_message(const uint64_t ident, const xpc_object_t params)
+{
+	return _mrc_xpc_create_command_message(ident, g_mrc_command_record_cache_flush, params);
+}
+
+//======================================================================================================================
+
+void
+mrc_xpc_record_cache_flush_params_set_record_name(const xpc_object_t params, const char * const record_name)
+{
+	xpc_dictionary_set_string(params, g_mrc_record_cache_flush_record_name, record_name);
+}
+
+//======================================================================================================================
+
+const char *
+mrc_xpc_record_cache_flush_params_get_record_name(const xpc_object_t params)
+{
+	return xpc_dictionary_get_string(params, g_mrc_record_cache_flush_record_name);
+}
+
+//======================================================================================================================
+
+void
+mrc_xpc_record_cache_flush_params_set_key_tag(const xpc_object_t params, const uint16_t key_tag)
+{
+	mdns_xpc_dictionary_set_uint16(params, g_mrc_record_cache_flush_key_tag, key_tag);
+}
+
+//======================================================================================================================
+
+uint16_t
+mrc_xpc_record_cache_flush_params_get_key_tag(const xpc_object_t params, bool * const out_valid)
+{
+	return mdns_xpc_dictionary_get_uint16(params, g_mrc_record_cache_flush_key_tag, out_valid);
+}
+
+//======================================================================================================================
 // MARK: - Internal Message Functions
 
 static xpc_object_t
diff --git a/mDNSMacOSX/libmrc/src/mrc_xpc.h b/mDNSMacOSX/libmrc/src/mrc_xpc.h
index c2542f3..433e9bf 100644
--- a/mDNSMacOSX/libmrc/src/mrc_xpc.h
+++ b/mDNSMacOSX/libmrc/src/mrc_xpc.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020-2022 Apple Inc. All rights reserved.
+ * Copyright (c) 2020-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -49,6 +49,42 @@
  */
 extern const char * const g_mrc_command_dns_proxy_get_state;
 
+/*!
+ *	@brief
+ *		DNS service registration start command string.
+ */
+extern const char * const g_mrc_command_dns_service_registration_start;
+
+/*!
+ *	@brief
+ *		DNS service registration stop command string.
+ */
+extern const char * const g_mrc_command_dns_service_registration_stop;
+
+/*!
+ *	@brief
+ *		Discovery proxy start command string.
+ */
+extern const char * const g_mrc_command_discovery_proxy_start;
+
+/*!
+ *	@brief
+ *		Discovery proxy stop command string.
+ */
+extern const char * const g_mrc_command_discovery_proxy_stop;
+
+/*!
+ *	@brief
+ *		Cached local record inquiry command string.
+ */
+extern const char * const g_mrc_command_cached_local_record_inquiry;
+
+/*!
+ *	@brief
+ *		Record cache flush command string.
+ */
+extern const char * const g_mrc_command_record_cache_flush;
+
 __BEGIN_DECLS
 
 /*!
@@ -337,6 +373,467 @@
 mdns_xpc_string_t _Nullable
 mrc_xpc_dns_proxy_state_result_get_description(mdns_xpc_dictionary_t result);
 
+/*!
+ *	@brief
+ *		Creates an XPC message for the DNS service registration start command.
+ *
+ *	@param ident
+ *		An ID number that uniquely identifies the new DNS service registration to start.
+ *
+ *	@param params
+ *		The DNS service registration start command's parameters dictionary.
+ *
+ *	@result
+ *		A reference to the message.
+ */
+XPC_RETURNS_RETAINED
+xpc_object_t
+mrc_xpc_create_dns_service_registration_start_command_message(uint64_t ident, xpc_object_t params);
+
+/*!
+ *	@brief
+ *		Creates an XPC message for the DNS service registration stop command.
+ *
+ *	@param ident
+ *		An ID number that identifies the DNS service registration to stop.
+ *
+ *	@result
+ *		A reference to the message.
+ *
+ *	@discussion
+ *		The ID number should match the ID number used in the DNS service registration start command message.
+ */
+XPC_RETURNS_RETAINED
+xpc_object_t
+mrc_xpc_create_dns_service_registration_stop_command_message(uint64_t ident);
+
+/*!
+ *	@brief
+ *		Sets the DNS service definition dictionary in a DNS service registration parameters dictionary.
+ *
+ *	@param params
+ *		The parameters dictionary.
+ *
+ *	@param dict
+ *		The DNS service definition dictionary which should be created with
+ *		mdns_dns_service_definition_create_xpc_dictionary().
+ */
+void
+mrc_xpc_dns_service_registration_params_set_defintion_dictionary(xpc_object_t params, xpc_object_t dict);
+
+/*!
+ *	@brief
+ *		Gets the DNS service definition dictionary from a DNS service registration parameters dictionary.
+ *
+ *	@param params
+ *		The parameters dictionary.
+ *
+ *	@result
+ *		The DNS service definition dictionary, if present. Otherwise, NULL.
+ *
+ *	@discussion
+ *		The resulting DNS service definition dictionary is meant to be passed to
+ *		mdns_dns_service_definition_create_from_xpc_dictionary() to create a DNS service definition.
+ */
+mdns_xpc_dictionary_t _Nullable
+mrc_xpc_dns_service_registration_params_get_defintion_dictionary(xpc_object_t params);
+
+MDNS_CLOSED_ENUM(mrc_dns_service_definition_type_t, uint8_t,
+	mrc_dns_service_definition_type_do53	= 1,
+	mrc_dns_service_definition_type_push	= 2,
+);
+
+/*!
+ *	@brief
+ *		Sets the DNS service definition type from a DNS service registration parameters dictionary.
+ *
+ *	@param params
+ *		The parameters dictionary.
+ *
+ *	@param type
+ *		The DNS service definition type.
+ */
+void
+mrc_xpc_dns_service_registration_params_set_definition_type(xpc_object_t params,
+	mrc_dns_service_definition_type_t type);
+
+/*!
+ *	@brief
+ *		Gets the DNS service definition type from a DNS service registration parameters dictionary.
+ *
+ *	@param params
+ *		The parameters dictionary.
+ *
+ *	@result
+ *		The DNS service definition type.
+ */
+mrc_dns_service_definition_type_t
+mrc_xpc_dns_service_registration_params_get_definition_type(xpc_object_t params, bool *out_valid);
+
+/*!
+ *	@brief
+ *		Specifies whether the registration should report the connection error, if the service being registered is a DNS push service.
+ *
+ *	@param params
+ *		The parameters dictionary.
+ *
+ *	@param reports_connection_errors
+ *		Whether a connection error should be reported.
+ */
+void
+mrc_xpc_dns_service_registration_params_set_reports_connection_errors(xpc_object_t params,
+	bool reports_connection_errors);
+
+/*!
+ *	@brief
+ *		Gets whether the registration should report the connection error, if the service being registered is a DNS push service.
+ *
+ *	@param params
+ *		The parameters dictionary.
+ *
+ *	@result
+ *		True if a connection error should be reported. Otherwise, false.
+ */
+bool
+mrc_xpc_dns_service_registration_params_get_reports_connection_errors(xpc_object_t params);
+
+/*!
+ *	@brief
+ *		Creates a push connection error notification body that can be used to send to the client.
+ *
+ *	@param connection_error
+ *		The connection error to report.
+ *
+ *	@param out_error
+ *		Pointer to an OSStatus variable, which will be set to the error that was encountered during creation.
+ *
+ *	@result
+ *		The notification body that can be decoded by mrc_xpc_dns_service_registration_notification_get_connection_error.
+ *		Or NULL if failure occurs during the creation.
+ */
+
+xpc_object_t
+mrc_xpc_dns_service_registration_notification_create_connection_error_body(OSStatus connection_error,
+	OSStatus *out_error);
+
+/*!
+ *	@brief
+ *		Gets the connection error code reported in the notification body.
+ *
+ *	@param notification_body
+ *		The notification body.
+ *
+ *	@param out_valid
+ *		A variable to set to true if the value is valid, or false if it isn't valid.
+ */
+OSStatus
+mrc_xpc_dns_service_registration_notification_get_connection_error(xpc_object_t notification_body, bool *out_valid);
+
+/*!
+ *	@brief
+ *		Creates an XPC message for the discovery proxy start command.
+ *
+ *	@param ident
+ *		An ID number that uniquely identifies the new discovery proxy to start.
+ *
+ *	@param params
+ *		The discovery proxy start command's parameters dictionary.
+ *
+ *	@result
+ *		A reference to the message.
+ */
+XPC_RETURNS_RETAINED
+xpc_object_t
+mrc_xpc_create_discovery_proxy_start_command_message(uint64_t ident, xpc_object_t params);
+
+/*!
+ *	@brief
+ *		Creates an XPC message for the discovery proxy stop command.
+ *
+ *	@param ident
+ *		An ID number that identifies the discovery proxy to stop.
+ *
+ *	@result
+ *		A reference to the message.
+ *
+ *	@discussion
+ *		The ID number should match the ID number used in the discovery proxy start command message.
+ */
+XPC_RETURNS_RETAINED
+xpc_object_t
+mrc_xpc_create_discovery_proxy_stop_command_message(uint64_t ident);
+
+/*!
+ *	@brief
+ *		Sets the index of the interface in a discovery proxy parameters dictionary.
+ *
+ *	@param params
+ *		The parameters dictionary.
+ *
+ *	@param ifindex
+ *		The interface index.
+ */
+void
+mrc_xpc_discovery_proxy_params_set_interface(xpc_object_t params, uint32_t ifindex);
+
+/*!
+ *	@brief
+ *		Adds an IP address to a discovery proxy parameters dictionary.
+ *
+ *	@param params
+ *		The parameters dictionary.
+ */
+void
+mrc_xpc_discovery_proxy_params_add_server_address(xpc_object_t params, const char *addr);
+
+/*!
+ *	@brief
+ *		Adds a matching domain to a discovery proxy parameters dictionary.
+ *
+ *	@param params
+ *		The parameters dictionary.
+ */
+void
+mrc_xpc_discovery_proxy_params_add_match_domain(xpc_object_t params, const char *domain);
+
+/*!
+ *	@brief
+ *		Adds a trusted TLS certificate to a discovery proxy parameters dictionary.
+ *
+ *	@param params
+ *		The parameters dictionary.
+ */
+void
+mrc_xpc_discovery_proxy_params_add_server_certificate(xpc_object_t params, const uint8_t *cert_data, size_t cert_len);
+
+/*!
+ *	@brief
+ *		Gets the interface index from a discovery proxy parameters dictionary.
+ *
+ *	@param params
+ *		The parameters dictionary.
+ *
+ *	@param out_valid
+ *		If non-NULL, the address of a variable to set to true if the interface index was properly set.
+ *		Otherwise, the variable is set to false.
+ *
+ *	@result
+ *		The interface index.
+ */
+uint32_t
+mrc_xpc_discovery_proxy_params_get_interface(xpc_object_t params, bool *out_valid);
+
+/*!
+ *	@brief
+ *		Gets an array of IP address strings from a discovery proxy parameters dictionary.
+ *
+ *	@param params
+ *		The parameters dictionary.
+ *
+ *	@result
+ *		The array if present. Otherwise, NULL.
+ */
+xpc_object_t _Nullable
+mrc_xpc_discovery_proxy_params_get_ip_address_strings(xpc_object_t params);
+
+/*!
+ *	@brief
+ *		Gets an array of matching domains from a discovery proxy parameters dictionary.
+ *
+ *	@param params
+ *		The parameters dictionary.
+ *
+ *	@result
+ *		The array if present. Otherwise, NULL.
+ */
+xpc_object_t _Nullable
+mrc_xpc_discovery_proxy_params_get_match_domains(xpc_object_t params);
+
+/*!
+ *	@brief
+ *		Gets an array of trusted TLS certificates from a discovery proxy parameters dictionary.
+ *
+ *	@param params
+ *		The parameters dictionary.
+ *
+ *	@result
+ *		The array if present. Otherwise, NULL.
+ */
+xpc_object_t _Nullable
+mrc_xpc_discovery_proxy_params_get_server_certificates(xpc_object_t params);
+
+/*!
+ *	@brief
+ *		Creates an XPC message for the cached local record inquiry command.
+ *
+ *	@param ident
+ *		An ID number that uniquely identifies the command.
+ *
+ *	@result
+ *		A reference to the message.
+ */
+XPC_RETURNS_RETAINED MDNS_WARN_RESULT
+xpc_object_t
+mrc_xpc_create_cached_local_record_inquiry_command_message(uint64_t ident);
+
+/*!
+ *	@brief
+ *		Adds information about a cached local record into a result dictionary.
+ *
+ *	@param result
+ *		The result dictionary.
+ *
+ *	@param name
+ *		The record's name in text representation as a C string.
+ *
+ *	@param rdata
+ *		The record's RDATA in text representation as a C string.
+ *
+ *	@param source_address
+ *		The source IPv4 or IPv6 address of the record in text representation as a C string.
+ */
+void
+mrc_xpc_cached_local_record_inquiry_result_add(xpc_object_t result, const char *name, const char * _Nullable rdata,
+	const char * _Nullable source_address);
+
+/*!
+ *	@brief
+ *		Gets the cached local record info array from a result dictionary, if any.
+ *
+ *	@param result
+ *		The result dictionary.
+ *
+ *	@param out_valid
+ *		If non-NULL, the address of a variable to set to true if the array was either properly set or missing.
+ *		Otherwise, the variable is set to false.
+ *
+ *	@result
+ *		The array if present. Otherwise, NULL.
+ *
+ *	@discussion
+ *		The array contains a dictionary for each cached local record. The dictionary consists of the name of the
+ *		record, its source IPv4 or IPv6 address in text representation, if available, and its RDATA in text
+ *		representation, if appropriate (currently limited to *._device-info._tcp.local TXT record RDATA).
+ */
+xpc_object_t _Nullable
+mrc_xpc_cached_local_record_inquiry_result_get_record_info(xpc_object_t result, bool * _Nullable out_valid);
+
+/*!
+ *	@brief
+ *		Creates an XPC message for the record cache flush command.
+ *
+ *	@param ident
+ *		An ID number that uniquely identifies the command.
+ *
+ *	@param params
+ *		The command's record cache flush parameters dictionary.
+ *
+ *	@result
+ *		A reference to the message.
+ */
+XPC_RETURNS_RETAINED MDNS_WARN_RESULT
+xpc_object_t
+mrc_xpc_create_record_cache_flush_command_message(uint64_t ident, xpc_object_t params);
+
+/*!
+ *	@brief
+ *		Sets the record name in a record cache flush parameters dictionary.
+ *
+ *	@param params
+ *		The parameters dictionary.
+ *
+ *	@param record_name
+ *		The record name as a C string.
+ */
+void
+mrc_xpc_record_cache_flush_params_set_record_name(xpc_object_t params, const char *record_name);
+
+/*!
+ *	@brief
+ *		Gets record name from a record cache flush parameters dictionary.
+ *
+ *	@param params
+ *		The parameters dictionary.
+ *
+ *	@result
+ *		The record name as a C string, if present. Otherwise, NULL.
+ */
+const char * _Nullable
+mrc_xpc_record_cache_flush_params_get_record_name(xpc_object_t params);
+
+/*!
+ *	@brief
+ *		Sets the key tag in a record cache flush parameters dictionary.
+ *
+ *	@param params
+ *		The parameters dictionary.
+ *
+ *	@param key_tag
+ *		The key tag.
+ */
+void
+mrc_xpc_record_cache_flush_params_set_key_tag(xpc_object_t params, uint16_t key_tag);
+
+/*!
+ *	@brief
+ *		Gets the key tag from a record cache flush parameters dictionary.
+ *
+ *	@param params
+ *		The parameters dictionary.
+ *
+ *	@param out_valid
+ *		If non-NULL, the address of a variable to set to true if the key tag was properly set. Otherwise, the
+ *		variable is set to false.
+ *
+ *	@result
+ *		The key tag.
+ */
+uint16_t
+mrc_xpc_record_cache_flush_params_get_key_tag(xpc_object_t params, bool *out_valid);
+
+/*!
+ *	@brief
+ *		Creates an XPC notification message.
+ *
+ *	@param ident
+ *		An ID number that identifies the command that the notification is associated with.
+ *
+ *	@param body
+ *		A dictionary that makes up the body of the notification message.
+ *
+ *	@result
+ *		A reference to the notification message.
+ */
+XPC_RETURNS_RETAINED
+xpc_object_t
+mrc_xpc_create_notification(uint64_t ident, xpc_object_t body);
+
+/*!
+ *	@brief
+ *		Gets the ID number from an XPC notification message.
+ *
+ *	@param notification
+ *		The notification message.
+ *
+ *	@result
+ *		The ID number, if present. Otherwise, 0.
+ */
+uint64_t
+mrc_xpc_notification_get_id(xpc_object_t notification);
+
+/*!
+ *	@brief
+ *		Gets the notification body dictionary from an XPC notification message.
+ *
+ *	@param notification
+ *		The notification message.
+ *
+ *	@result
+ *		The notification body, if present. Otherwise, NULL.
+ */
+xpc_object_t _Nullable
+mrc_xpc_notification_get_body(xpc_object_t notification);
+
 __END_DECLS
 
 MDNS_ASSUME_NONNULL_END
diff --git a/mDNSMacOSX/libmrc/src/mrcs_dns_proxy.c b/mDNSMacOSX/libmrc/src/mrcs_dns_proxy.c
index 5fee380..6235544 100644
--- a/mDNSMacOSX/libmrc/src/mrcs_dns_proxy.c
+++ b/mDNSMacOSX/libmrc/src/mrcs_dns_proxy.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2021-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -270,7 +270,7 @@
 	const nw_nat64_prefix_t * const prefix = mrcs_dns_proxy_get_nat64_prefix(me);
 	if (prefix) {
 		uint8_t ipv6_addr[16] = {0};
-		check_compile_time_code(sizeof(prefix->data) <= sizeof(ipv6_addr));
+		mdns_compile_time_check_local(sizeof(prefix->data) <= sizeof(ipv6_addr));
 		memcpy(ipv6_addr, prefix->data, sizeof(prefix->data));
 
 		char addr_buf[INET6_ADDRSTRLEN];
@@ -589,11 +589,11 @@
 {
 	int len;
 	switch (prefix->length) {
-#define _nat64_prefix_length_case(BITLEN)					\
-		case nw_nat64_prefix_length_ ## BITLEN: {			\
-			check_compile_time_code(((BITLEN) % 8) == 0);	\
-			len = (BITLEN) / 8;								\
-			break;											\
+#define _nat64_prefix_length_case(BITLEN)						\
+		case nw_nat64_prefix_length_ ## BITLEN: {				\
+			mdns_compile_time_check_local(((BITLEN) % 8) == 0);	\
+			len = (BITLEN) / 8;									\
+			break;												\
 		}
 		_nat64_prefix_length_case(32)
 		_nat64_prefix_length_case(40)
@@ -602,9 +602,7 @@
 		_nat64_prefix_length_case(64)
 		_nat64_prefix_length_case(96)
 #undef _nat64_prefix_length_case
-		CUClangWarningIgnoreBegin(-Wcovered-switch-default);
-		default:
-		CUClangWarningIgnoreEnd();
+		MDNS_COVERED_SWITCH_DEFAULT:
 			len = -1;
 			break;
 	}
diff --git a/mDNSMacOSX/libmrc/src/mrcs_object.c b/mDNSMacOSX/libmrc/src/mrcs_object.c
index 120887a..5b65c72 100644
--- a/mDNSMacOSX/libmrc/src/mrcs_object.c
+++ b/mDNSMacOSX/libmrc/src/mrcs_object.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021-2022 Apple Inc. All rights reserved.
+ * Copyright (c) 2021-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
  */
 
 #include "mrcs_object.h"
+#include "mrcs_object_internal.h"
 
 #include "mdns_obj.h"
 #include "mdns_strict.h"
@@ -22,6 +23,14 @@
 //======================================================================================================================
 // MARK: - Object Public Methods
 
+mdns_kind_t
+mrcs_get_kind(const mrcs_object_t me)
+{
+	return mdns_obj_get_kind(me);
+}
+
+//======================================================================================================================
+
 void
 mrcs_retain(const mrcs_object_t me)
 {
diff --git a/mDNSMacOSX/libmrc/src/mrcs_object_internal.h b/mDNSMacOSX/libmrc/src/mrcs_object_internal.h
new file mode 100644
index 0000000..52193ff
--- /dev/null
+++ b/mDNSMacOSX/libmrc/src/mrcs_object_internal.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MRCS_OBJECT_INTERNAL_H
+#define MRCS_OBJECT_INTERNAL_H
+
+#include <mdns/base.h>
+#include <mdns/object.h>
+
+#include "mdns_obj.h"
+
+MDNS_ASSUME_NONNULL_BEGIN
+
+__BEGIN_DECLS
+
+/*!
+ *	@brief
+ *		Gets a reference to an object's kind.
+ *
+ *	@param object
+ *		The object.
+ *
+ *	@discussion
+ *		This function is meant only for object implementers.
+ */
+mdns_kind_t
+mrcs_get_kind(mrcs_any_t object);
+
+__END_DECLS
+
+MDNS_ASSUME_NONNULL_END
+
+#endif	// MRCS_OBJECT_INTERNAL_H
diff --git a/mDNSMacOSX/libmrc/src/mrcs_object_members.h b/mDNSMacOSX/libmrc/src/mrcs_object_members.h
index 5790703..99c465d 100644
--- a/mDNSMacOSX/libmrc/src/mrcs_object_members.h
+++ b/mDNSMacOSX/libmrc/src/mrcs_object_members.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 Apple Inc. All rights reserved.
+ * Copyright (c) 2021-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -21,11 +21,12 @@
 
 #define MRCS_UNION_MEMBER(NAME)	struct mrcs_ ## NAME ## _s *	_mrcs_ ## NAME
 
-#define MRCS_OBJECT_MEMBERS					\
-	MRCS_UNION_MEMBER(object);				\
-	MRCS_UNION_MEMBER(dns_proxy);			\
-	MRCS_UNION_MEMBER(dns_proxy_manager);	\
-	MRCS_UNION_MEMBER(dns_proxy_request);	\
+#define MRCS_OBJECT_MEMBERS									\
+	MRCS_UNION_MEMBER(object);								\
+	MRCS_UNION_MEMBER(dns_proxy);							\
+	MRCS_UNION_MEMBER(dns_proxy_manager);					\
+	MRCS_UNION_MEMBER(dns_proxy_request);					\
+	MRCS_UNION_MEMBER(dns_service_registration_request);	\
 	MRCS_UNION_MEMBER(session);
 
 #endif	// MRCS_OBJECT_MEMBERS_H
diff --git a/mDNSMacOSX/libmrc/src/mrcs_objects.m b/mDNSMacOSX/libmrc/src/mrcs_objects.m
index f90405d..52d7396 100644
--- a/mDNSMacOSX/libmrc/src/mrcs_objects.m
+++ b/mDNSMacOSX/libmrc/src/mrcs_objects.m
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 Apple Inc. All rights reserved.
+ * Copyright (c) 2021-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -41,4 +41,5 @@
 MRCS_OBJC_CLASS_IMPLEMENTATION(dns_proxy);
 MRCS_OBJC_CLASS_IMPLEMENTATION(dns_proxy_manager);
 MRCS_OBJC_CLASS_IMPLEMENTATION(dns_proxy_request);
+MRCS_OBJC_CLASS_IMPLEMENTATION(dns_service_registration_request);
 MRCS_OBJC_CLASS_IMPLEMENTATION(session);
diff --git a/mDNSMacOSX/libmrc/src/mrcs_server.c b/mDNSMacOSX/libmrc/src/mrcs_server.c
index 0a749cc..e318671 100644
--- a/mDNSMacOSX/libmrc/src/mrcs_server.c
+++ b/mDNSMacOSX/libmrc/src/mrcs_server.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2021-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -16,28 +16,49 @@
 
 #include "mrcs_server_internal.h"
 
+#include "helpers.h"
 #include "mrc_xpc.h"
+#include "mrcs_object_internal.h"
 #include "mrcs_objects.h"
 
 #include <CoreUtils/CoreUtils.h>
+#include <mdns/DNSMessage.h>
+#include <mdns/string_builder.h>
 #include <mdns/xpc.h>
 #include <xpc/xpc.h>
 #include "mdns_strict.h"
 
 //======================================================================================================================
+// MARK: - Constants
+
+MDNS_CLOSED_ENUM(mrcs_command_t, uint8_t,
+	mrcs_command_invalid						= 0,
+	mrcs_command_dns_proxy_start				= 1,
+	mrcs_command_dns_proxy_stop					= 2,
+	mrcs_command_dns_proxy_get_state			= 3,
+	mrcs_command_dns_service_registration_start	= 4,
+	mrcs_command_dns_service_registration_stop	= 5,
+	mrcs_command_discovery_proxy_start			= 6,
+	mrcs_command_discovery_proxy_stop			= 7,
+	mrcs_command_cached_local_record_inquiry	= 8,
+	mrcs_command_record_cache_flush				= 9,
+);
+
+//======================================================================================================================
 // MARK: - Session Kind
 
 struct mrcs_session_s {
-	struct mdns_obj_s			base;			// Object base.
-	mrcs_session_t				next;			// Next session in list.
-	xpc_connection_t			connection;		// Underlying XPC connection.
-	mrcs_dns_proxy_request_t	request_list;	// List of client requests.
+	struct mdns_obj_s						base;							// Object base.
+	mrcs_session_t							next;							// Next session in list.
+	xpc_connection_t						connection;						// Underlying XPC connection.
+	mrcs_dns_proxy_request_t				dns_proxy_request_list;			// List of client requests.
+	mrcs_dns_service_registration_request_t	dns_service_reg_request_list;	// List of DNS service requests.
 };
 
 MRCS_OBJECT_SUBKIND_DEFINE(session);
 
 //======================================================================================================================
-// MARK: - Session Kind
+// MARK: - DNS Proxy Request Kind
 
 struct mrcs_dns_proxy_request_s {
 	struct mdns_obj_s			base;		// Object base.
@@ -49,6 +70,20 @@
 MRCS_OBJECT_SUBKIND_DEFINE(dns_proxy_request);
 
 //======================================================================================================================
+// MARK: - DNS Service Registration Request Kind
+
+struct mrcs_dns_service_registration_request_s {
+	struct mdns_obj_s						base;				// Object base.
+	mrcs_dns_service_registration_request_t	next;				// Next request in list.
+	mdns_dns_service_definition_t			do53_definition;	// DNS over 53 service definition.
+	mdns_dns_push_service_definition_t		push_definition;	// DNS push service definition.
+	uint64_t								command_id;			// Command ID.
+	mdns_dns_service_id_t					ident;				// DNS service ID.
+};
+
+MRCS_OBJECT_SUBKIND_DEFINE(dns_service_registration_request);
+
+//======================================================================================================================
 // MARK: - Local Prototypes
 
 static dispatch_queue_t
@@ -82,38 +117,95 @@
 _mrcs_create_dns_proxy_from_params_dictionary(xpc_object_t params, OSStatus *out_error);
 
 //======================================================================================================================
+// MARK: - Logging
+
+MDNS_LOG_CATEGORY_DEFINE(server, "mrcs_server");
+
+//======================================================================================================================
 // MARK: - Globals
 
-static mrcs_server_handlers_t	g_handlers		= NULL;
-static mrcs_session_t			g_session_list	= NULL;
+static mrcs_server_dns_proxy_handlers_t					g_dns_proxy_handlers				= NULL;
+static mrcs_server_dns_service_registration_handlers_t	g_dns_service_registration_handlers	= NULL;
+static mrcs_server_discovery_proxy_handlers_t			g_discovery_proxy_handlers			= NULL;
+static mrcs_server_record_cache_handlers_t				g_record_cache_handlers				= NULL;
+static mrcs_session_t									g_session_list						= NULL;
+static mrcs_session_t									g_current_discovery_proxy_owner		= NULL;
+static bool												g_activated							= false;
 
 //======================================================================================================================
 // MARK: - Public Server Functions
 
-OSStatus
-mrcs_server_init(const mrcs_server_handlers_t handlers)
+void
+mrcs_server_set_dns_proxy_handlers(const mrcs_server_dns_proxy_handlers_t handlers)
 {
-	OSStatus err;
-	static xpc_connection_t s_listener = NULL;
-	require_action_quiet(!s_listener, exit, err = kNoErr);
-
-	const uint64_t flags = XPC_CONNECTION_MACH_SERVICE_LISTENER;
-	s_listener = xpc_connection_create_mach_service(g_mrc_mach_service_name, _mrcs_server_queue(), flags);
-	require_action_quiet(s_listener, exit, err = kNoResourcesErr);
-
-	xpc_connection_set_event_handler(s_listener,
-	^(const xpc_object_t event)
-	{
-		if (xpc_get_type(event) == XPC_TYPE_CONNECTION) {
-			_mrcs_server_handle_new_connection((xpc_connection_t)event);
-		}
+	dispatch_async(_mrcs_server_queue(),
+	^{
+		mdns_require_return(!g_activated);
+		g_dns_proxy_handlers = handlers;
 	});
-	g_handlers = handlers;
-	xpc_connection_activate(s_listener);
-	err = kNoErr;
+}
 
-exit:
-	return err;
+//======================================================================================================================
+
+void
+mrcs_server_set_dns_service_registration_handlers(const mrcs_server_dns_service_registration_handlers_t handlers)
+{
+	dispatch_async(_mrcs_server_queue(),
+	^{
+		mdns_require_return(!g_activated);
+		g_dns_service_registration_handlers = handlers;
+	});
+}
+
+//======================================================================================================================
+
+void
+mrcs_server_set_discovery_proxy_handlers(const mrcs_server_discovery_proxy_handlers_t handlers)
+{
+	dispatch_async(_mrcs_server_queue(),
+	^{
+		mdns_require_return(!g_activated);
+		g_discovery_proxy_handlers = handlers;
+	});
+}
+
+//======================================================================================================================
+
+void
+mrcs_server_set_record_cache_handlers(const mrcs_server_record_cache_handlers_t handlers)
+{
+	dispatch_async(_mrcs_server_queue(),
+	^{
+		mdns_require_return(!g_activated);
+		g_record_cache_handlers = handlers;
+	});
+}
+
+//======================================================================================================================
+
+void
+mrcs_server_activate(void)
+{
+	dispatch_async(_mrcs_server_queue(),
+	^{
+		static xpc_connection_t s_listener = NULL;
+		mdns_require_return(!s_listener);
+
+		const uint64_t flags = XPC_CONNECTION_MACH_SERVICE_LISTENER;
+		s_listener = xpc_connection_create_mach_service(g_mrc_mach_service_name, _mrcs_server_queue(), flags);
+		mdns_require_return_action(s_listener, os_log_fault(_mdns_server_log(),
+			"Failed to create XPC listener for %{public}s", g_mrc_mach_service_name));
+
+		xpc_connection_set_event_handler(s_listener,
+		^(const xpc_object_t event)
+		{
+			if (xpc_get_type(event) == XPC_TYPE_CONNECTION) {
+				_mrcs_server_handle_new_connection((xpc_connection_t)event);
+			}
+		});
+		xpc_connection_activate(s_listener);
+		g_activated = true;
+	});
 }
 
 //======================================================================================================================
@@ -152,8 +244,8 @@
 _mrcs_server_dns_proxy_start(const mrcs_dns_proxy_t proxy)
 {
 	OSStatus err;
-	if (g_handlers->dns_proxy_start) {
-		err = g_handlers->dns_proxy_start(proxy);
+	if (g_dns_proxy_handlers->start) {
+		err = g_dns_proxy_handlers->start(proxy);
 	} else {
 		err = kNotHandledErr;
 	}
@@ -166,8 +258,8 @@
 _mrcs_server_dns_proxy_stop(const mrcs_dns_proxy_t proxy)
 {
 	OSStatus err;
-	if (g_handlers->dns_proxy_stop) {
-		err = g_handlers->dns_proxy_stop(proxy);
+	if (g_dns_proxy_handlers->stop) {
+		err = g_dns_proxy_handlers->stop(proxy);
 	} else {
 		err = kNotHandledErr;
 	}
@@ -181,9 +273,8 @@
 {
 	char *state;
 	OSStatus err;
-	const mrcs_server_dns_proxy_get_state_handler_f dns_proxy_get_state = g_handlers->dns_proxy_get_state;
-	if (dns_proxy_get_state) {
-		state = dns_proxy_get_state();
+	if (g_dns_proxy_handlers->get_state) {
+		state = g_dns_proxy_handlers->get_state();
 		err = state ? kNoErr : kNoMemoryErr;
 	} else {
 		state = NULL;
@@ -196,6 +287,187 @@
 }
 
 //======================================================================================================================
+
+static OSStatus
+_mrcs_server_discovery_proxy_start(const uint32_t ifindex, const CFArrayRef addresses, const CFArrayRef match_domains,
+	const CFArrayRef server_certificates)
+{
+	OSStatus err;
+	if (g_discovery_proxy_handlers->start) {
+		err = g_discovery_proxy_handlers->start(ifindex, addresses, match_domains, server_certificates);
+	} else {
+		err = kNotHandledErr;
+	}
+	return err;
+}
+
+//======================================================================================================================
+
+static OSStatus
+_mrcs_server_discovery_proxy_stop(void)
+{
+	OSStatus err;
+	if (g_discovery_proxy_handlers->stop) {
+		err = g_discovery_proxy_handlers->stop();
+	} else {
+		err = kNotHandledErr;
+	}
+	return err;
+}
+
+//======================================================================================================================
+
+static mdns_dns_service_id_t
+_mrcs_server_dns_service_registration_start_with_connection_error_handler(
+	const mdns_any_dns_service_definition_t definition, const dispatch_queue_t connection_error_queue,
+	const mdns_event_handler_t connection_error_handler, OSStatus * const out_error)
+{
+	OSStatus err;
+	mdns_dns_service_id_t ident;
+	if (g_dns_service_registration_handlers->start) {
+		ident = g_dns_service_registration_handlers->start(definition, connection_error_queue,
+			connection_error_handler);
+		err = (ident != MDNS_DNS_SERVICE_INVALID_ID) ? kNoErr : kUnknownErr;
+	} else {
+		ident = MDNS_DNS_SERVICE_INVALID_ID;
+		err = kNotHandledErr;
+	}
+	mdns_assign(out_error, err);
+	return ident;
+}
+
+//======================================================================================================================
+
+static mdns_dns_service_id_t
+_mrcs_server_dns_service_registration_start(const mdns_any_dns_service_definition_t definition,
+	OSStatus * const out_error)
+{
+	return _mrcs_server_dns_service_registration_start_with_connection_error_handler(definition, NULL, NULL, out_error);
+}
+
+//======================================================================================================================
+
+static OSStatus
+_mrcs_server_dns_service_registration_stop(const mdns_dns_service_id_t ident)
+{
+	OSStatus err;
+	if (g_dns_service_registration_handlers->stop) {
+		g_dns_service_registration_handlers->stop(ident);
+		err = kNoErr;
+	} else {
+		err = kNotHandledErr;
+	}
+	return err;
+}
+
+//======================================================================================================================
+
+static OSStatus
+_mrcs_server_record_cache_enumerate_local_records(const mrcs_record_applier_t applier)
+{
+	OSStatus err;
+	if (g_record_cache_handlers->enumerate_local_records) {
+		g_record_cache_handlers->enumerate_local_records(applier);
+		err = kNoErr;
+	} else {
+		err = kNotHandledErr;
+	}
+	return err;
+}
+
+//======================================================================================================================
+
+static OSStatus
+_mrcs_server_record_cache_flush_by_name(const char * const record_name)
+{
+	OSStatus err;
+	if (g_record_cache_handlers->flush_by_name) {
+		g_record_cache_handlers->flush_by_name(record_name);
+		err = kNoErr;
+	} else {
+		err = kNotHandledErr;
+	}
+	return err;
+}
+
+//======================================================================================================================
+
+static OSStatus
+_mrcs_server_record_cache_flush_by_name_and_key_tag(const char * const record_name, const uint16_t key_tag)
+{
+	OSStatus err;
+	if (g_record_cache_handlers->flush_by_name_and_key_tag) {
+		g_record_cache_handlers->flush_by_name_and_key_tag(record_name, key_tag);
+		err = kNoErr;
+	} else {
+		err = kNotHandledErr;
+	}
+	return err;
+}
+
+//======================================================================================================================
+
+typedef struct {
+	const char *	string;
+	mrcs_command_t	code;
+} mrcs_command_string_code_pair_t;
+
+static mrcs_command_t
+_mrcs_server_command_string_to_code(const char * const command_str)
+{
+#define MRCS_SERVER_COMMAND_STRING_CODE_PAIR(NAME)	{g_mrc_command_ ## NAME, mrcs_command_ ## NAME}
+	const mrcs_command_string_code_pair_t pairs[] = {
+		MRCS_SERVER_COMMAND_STRING_CODE_PAIR(cached_local_record_inquiry),
+		MRCS_SERVER_COMMAND_STRING_CODE_PAIR(discovery_proxy_start),
+		MRCS_SERVER_COMMAND_STRING_CODE_PAIR(discovery_proxy_stop),
+		MRCS_SERVER_COMMAND_STRING_CODE_PAIR(dns_proxy_start),
+		MRCS_SERVER_COMMAND_STRING_CODE_PAIR(dns_proxy_stop),
+		MRCS_SERVER_COMMAND_STRING_CODE_PAIR(dns_proxy_get_state),
+		MRCS_SERVER_COMMAND_STRING_CODE_PAIR(dns_service_registration_start),
+		MRCS_SERVER_COMMAND_STRING_CODE_PAIR(dns_service_registration_stop),
+		MRCS_SERVER_COMMAND_STRING_CODE_PAIR(record_cache_flush),
+	};
+	for (size_t i = 0; i < mdns_countof(pairs); ++i) {
+		const mrcs_command_string_code_pair_t * const pair = &pairs[i];
+		if (strcmp(command_str, pair->string) == 0) {
+			return pair->code;
+		}
+	}
+	return mrcs_command_invalid;
+}
+
+//======================================================================================================================
+
+static const char *
+_mrcs_server_get_required_entitlement(const mrcs_command_t command)
+{
+	switch (command) {
+		case mrcs_command_discovery_proxy_start:
+		case mrcs_command_discovery_proxy_stop:
+			return "com.apple.mDNSResponder.discovery-proxy";
+
+		case mrcs_command_dns_proxy_start:
+		case mrcs_command_dns_proxy_stop:
+		case mrcs_command_dns_proxy_get_state:
+			return "com.apple.mDNSResponder.dnsproxy";
+
+		case mrcs_command_dns_service_registration_start:
+		case mrcs_command_dns_service_registration_stop:
+			return "com.apple.mDNSResponder.dns-service-registration";
+
+		case mrcs_command_cached_local_record_inquiry:
+			return "com.apple.mDNSResponder.record-cache.local-record-info";
+
+		case mrcs_command_record_cache_flush:
+			return "com.apple.private.mDNSResponder.record-cache.flush";
+
+		case mrcs_command_invalid:
+		MDNS_COVERED_SWITCH_DEFAULT:
+			return NULL;
+	}
+}
+
+//======================================================================================================================
 // MARK: - Session Methods
 
 static mrcs_session_t
@@ -270,12 +542,23 @@
 _mrcs_session_invalidate(const mrcs_session_t me)
 {
 	xpc_connection_forget(&me->connection);
-	mrcs_dns_proxy_request_t request;
-	while ((request = me->request_list) != NULL) {
-		me->request_list = request->next;
+	while (me->dns_proxy_request_list) {
+		mrcs_dns_proxy_request_t request = me->dns_proxy_request_list;
+		me->dns_proxy_request_list = request->next;
 		_mrcs_server_dns_proxy_stop(request->proxy);
 		mrcs_forget(&request);
 	}
+	while (me->dns_service_reg_request_list) {
+		mrcs_dns_service_registration_request_t request = me->dns_service_reg_request_list;
+		me->dns_service_reg_request_list = request->next;
+		_mrcs_server_dns_service_registration_stop(request->ident);
+		mrcs_forget(&request);
+	}
+	// Handle discovery proxy invalidation.
+	if (g_current_discovery_proxy_owner == me) {
+		_mrcs_server_discovery_proxy_stop();
+		mrcs_forget(&g_current_discovery_proxy_owner);
+	}
 }
 
 //======================================================================================================================
@@ -316,7 +599,7 @@
 	OSStatus err;
 	mrcs_dns_proxy_t proxy = NULL;
 	const uint64_t command_id = mrc_xpc_message_get_id(msg);
-	mrcs_dns_proxy_request_t *ptr = &me->request_list;
+	mrcs_dns_proxy_request_t *ptr = &me->dns_proxy_request_list;
 	while (*ptr && ((*ptr)->command_id != command_id)) {
 		ptr = &(*ptr)->next;
 	}
@@ -350,7 +633,7 @@
 {
 	OSStatus err;
 	const uint64_t command_id = mrc_xpc_message_get_id(msg);
-	mrcs_dns_proxy_request_t *ptr = &me->request_list;
+	mrcs_dns_proxy_request_t *ptr = &me->dns_proxy_request_list;
 	while (*ptr && ((*ptr)->command_id != command_id)) {
 		ptr = &(*ptr)->next;
 	}
@@ -391,24 +674,405 @@
 
 //======================================================================================================================
 
+static mrcs_dns_service_registration_request_t *
+_mrcs_session_get_dns_service_registration(const mrcs_session_t me, const uint64_t command_id)
+{
+	mrcs_dns_service_registration_request_t *ptr = &me->dns_service_reg_request_list;
+	while (*ptr && ((*ptr)->command_id != command_id)) {
+		ptr = &(*ptr)->next;
+	}
+	return ptr;
+}
+
+//======================================================================================================================
+
+static xpc_object_t
+mrc_xpc_dns_service_registration_create_connection_error_notification(const uint64_t command_id,
+	const OSStatus connection_error, OSStatus * const out_error)
+{
+	OSStatus err;
+	xpc_object_t body = NULL;
+	xpc_object_t notification = NULL;
+
+	body = mrc_xpc_dns_service_registration_notification_create_connection_error_body(connection_error, &err);
+	mdns_require_noerr_quiet(err, exit);
+
+	notification = mrc_xpc_create_notification(command_id, body);
+	mdns_require_action_quiet(notification, exit, err = kNoResourcesErr);
+	err = kNoErr;
+
+exit:
+	mdns_assign(out_error, err);
+	xpc_forget(&body);
+	return notification;
+}
+
+//======================================================================================================================
+
+static void
+_mrcs_session_handle_dns_service_registration_connection_error(const mrcs_session_t me, const uint64_t command_id,
+	const OSStatus connection_error)
+{
+	if (me->connection && _mrcs_session_get_dns_service_registration(me, command_id)) {
+		OSStatus err;
+		xpc_object_t notification =
+			mrc_xpc_dns_service_registration_create_connection_error_notification(command_id, connection_error, &err);
+		if (err == kNoErr) {
+			xpc_connection_send_message(me->connection, notification);
+		}
+		mdns_forget(&notification);
+	}
+}
+
+//======================================================================================================================
+
+static OSStatus
+_mrcs_session_handle_dns_service_registration_start(const mrcs_session_t me, const xpc_object_t msg)
+{
+	OSStatus err;
+	mrcs_dns_service_registration_request_t new_request = NULL;
+	const uint64_t command_id = mrc_xpc_message_get_id(msg);
+	mrcs_dns_service_registration_request_t * const ptr = _mrcs_session_get_dns_service_registration(me, command_id);
+	mrcs_dns_service_registration_request_t request = *ptr;
+	mdns_require_action_quiet(!request, exit, err = kAlreadyInUseErr);
+
+	const xpc_object_t params = mrc_xpc_message_get_params(msg);
+	mdns_require_action_quiet(params, exit, err = kParamErr);
+
+	const mdns_xpc_dictionary_t def_dict = mrc_xpc_dns_service_registration_params_get_defintion_dictionary(params);
+	mdns_require_action_quiet(def_dict, exit, err = kParamErr);
+
+	bool type_valid = false;
+	const mrc_dns_service_definition_type_t def_type =
+		mrc_xpc_dns_service_registration_params_get_definition_type(params, &type_valid);
+	mdns_require_action_quiet(type_valid, exit, err = kParamErr);
+
+	new_request = _mrcs_dns_service_registration_request_new();
+	require_action_quiet(new_request, exit, err = kNoMemoryErr);
+
+	switch (def_type) {
+		case mrc_dns_service_definition_type_do53:
+			new_request->do53_definition = mdns_dns_service_definition_create_from_xpc_dictionary(def_dict, &err);
+			mdns_require_noerr_quiet(err, exit);
+
+			new_request->ident = _mrcs_server_dns_service_registration_start(new_request->do53_definition, &err);
+			mdns_require_noerr_quiet(err, exit);
+			break;
+
+		case mrc_dns_service_definition_type_push:
+		{
+			new_request->push_definition = mdns_dns_push_service_definition_create_from_xpc_dictionary(def_dict, &err);
+			mdns_require_noerr_quiet(err, exit);
+
+			const bool reports_connection_errors =
+				mrc_xpc_dns_service_registration_params_get_reports_connection_errors(params);
+
+			dispatch_queue_t connection_error_queue = NULL;
+			mdns_event_handler_t connection_error_handler = NULL;
+			if (reports_connection_errors) {
+				connection_error_queue = _mrcs_server_queue();
+				connection_error_handler =
+				^(const mdns_event_t event, const OSStatus error)
+				{
+					switch (event) {
+						case mdns_event_invalidated:
+							mrcs_release(me);
+							break;
+
+						case mdns_event_error:
+							_mrcs_session_handle_dns_service_registration_connection_error(me, command_id, error);
+							break;
+
+						case mdns_event_update:
+							// Not handled for now.
+							break;
+					}
+				};
+			}
+			new_request->ident = _mrcs_server_dns_service_registration_start_with_connection_error_handler(
+				new_request->push_definition, connection_error_queue, connection_error_handler, &err);
+			mdns_require_noerr_quiet(err, exit);
+			if (reports_connection_errors) {
+				// Only retain the object if the registration has succeeded.
+				mrcs_retain(me);
+			}
+			break;
+		}
+
+		MDNS_COVERED_SWITCH_DEFAULT:
+			err = kParamErr;
+			goto exit;
+	}
+	new_request->command_id = command_id;
+	*ptr = new_request;
+	new_request = NULL;
+
+exit:
+	mrcs_forget(&new_request);
+	return err;
+}
+
+//======================================================================================================================
+
+static OSStatus
+_mrcs_session_handle_dns_service_registration_stop(const mrcs_session_t me, const xpc_object_t msg)
+{
+	OSStatus err;
+	const uint64_t command_id = mrc_xpc_message_get_id(msg);
+	mrcs_dns_service_registration_request_t * const ptr = _mrcs_session_get_dns_service_registration(me, command_id);
+	mrcs_dns_service_registration_request_t request = *ptr;
+	mdns_require_action_quiet(request, exit, err = kIDErr);
+
+	*ptr = request->next;
+	request->next = NULL;
+	err = _mrcs_server_dns_service_registration_stop(request->ident);
+	mrcs_forget(&request);
+	mdns_require_noerr_quiet(err, exit);
+
+exit:
+	return err;
+}
+
+//======================================================================================================================
+
+static OSStatus
+_mrcs_session_handle_discovery_proxy_start(const mrcs_session_t me, const xpc_object_t msg)
+{
+	OSStatus err;
+	CFMutableArrayRef addresses = NULL;
+	CFMutableArrayRef domains = NULL;
+	CFMutableArrayRef certificates = NULL;
+
+	mdns_require_action_quiet(!g_current_discovery_proxy_owner, exit, err = kAlreadyInitializedErr);
+
+	const xpc_object_t params = mrc_xpc_message_get_params(msg);
+	mdns_require_action_quiet(params, exit, err = kParamErr);
+
+	// Get interface index.
+	bool valid;
+	const uint32_t ifindex = mrc_xpc_discovery_proxy_params_get_interface(params, &valid);
+	mdns_require_action_quiet(valid, exit, err = kParamErr);
+
+	// Get address list.
+	const xpc_object_t addr_str_array = mrc_xpc_discovery_proxy_params_get_ip_address_strings(params);
+	mdns_require_action_quiet(addr_str_array, exit, err = kParamErr);
+
+	const size_t addr_count = xpc_array_get_count(addr_str_array);
+	mdns_require_action_quiet(addr_count > 0, exit, err = kParamErr);
+
+	addresses = CFArrayCreateMutable(kCFAllocatorDefault, (CFIndex)addr_count, &mdns_cfarray_callbacks);
+	mdns_require_action_quiet(addresses, exit, err = kNoResourcesErr);
+
+	for (size_t i = 0; i < addr_count; ++i) {
+		const char * const addr_str = xpc_array_get_string(addr_str_array, i);
+		mdns_require_action_quiet(addr_str, exit, err = kParamErr);
+
+		mdns_address_t addr = mdns_address_create_from_ip_address_string(addr_str);
+		mdns_require_action_quiet(addr, exit, err = kParamErr);
+
+		CFArrayAppendValue(addresses, addr);
+		mdns_forget(&addr);
+	}
+
+	// Get domain list.
+	const xpc_object_t domain_array = mrc_xpc_discovery_proxy_params_get_match_domains(params);
+	mdns_require_action_quiet(domain_array, exit, err = kParamErr);
+
+	const size_t domain_count = xpc_array_get_count(domain_array);
+	mdns_require_action_quiet(domain_count > 0, exit, err = kParamErr);
+
+	domains = CFArrayCreateMutable(kCFAllocatorDefault, 0, &mdns_cfarray_callbacks);
+	mdns_require_action_quiet(domains, exit, err = kNoResourcesErr);
+
+	for (size_t i = 0; i < domain_count; ++i) {
+		const char * const domain_str = xpc_array_get_string(domain_array, i);
+		mdns_require_action_quiet(domain_str, exit, err = kParamErr);
+
+		mdns_domain_name_t domain = mdns_domain_name_create(domain_str, mdns_domain_name_create_opts_none, &err);
+		mdns_require_noerr_quiet(err, exit);
+
+		CFArrayAppendValue(domains, domain);
+		mdns_forget(&domain);
+	}
+
+	// Get certificate list.
+	const xpc_object_t cert_array = mrc_xpc_discovery_proxy_params_get_server_certificates(params);
+	mdns_require_action_quiet(domain_array, exit, err = kParamErr);
+
+	const size_t cert_count = xpc_array_get_count(cert_array);
+	mdns_require_action_quiet(cert_count > 0, exit, err = kParamErr);
+
+	certificates = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
+	mdns_require_action_quiet(certificates, exit, err = kParamErr);
+
+	for (size_t i = 0; i < cert_count; ++i) {
+		size_t data_len = 0;
+		const uint8_t * const data_ptr = xpc_array_get_data(cert_array, i, &data_len);
+		mdns_require_action_quiet(data_ptr && (data_len > 0), exit, err = kParamErr);
+
+		CFDataRef cert_cf_data = CFDataCreate(kCFAllocatorDefault, data_ptr, (CFIndex)data_len);
+		mdns_require_action_quiet(cert_cf_data, exit, err = kNoResourcesErr);
+
+		CFArrayAppendValue(certificates, cert_cf_data);
+		CFForget(&cert_cf_data);
+	}
+
+	err = _mrcs_server_discovery_proxy_start(ifindex, addresses, domains, certificates);
+	mdns_require_noerr_quiet(err, exit);
+
+	g_current_discovery_proxy_owner = me;
+	mrcs_retain(g_current_discovery_proxy_owner);
+
+exit:
+	CFForget(&addresses);
+	CFForget(&domains);
+	CFForget(&certificates);
+	return err;
+}
+
+//======================================================================================================================
+
+static OSStatus
+_mrcs_session_handle_discovery_proxy_stop(const mrcs_session_t me, __unused const xpc_object_t msg)
+{
+	OSStatus err = kNotInitializedErr;
+	if (g_current_discovery_proxy_owner == me) {
+		err = _mrcs_server_discovery_proxy_stop();
+		mrcs_forget(&g_current_discovery_proxy_owner);
+	}
+	return err;
+}
+
+//======================================================================================================================
+
 static mdns_xpc_dictionary_t
-_mrcs_session_handle_command(const mrcs_session_t me, const char * const command, const xpc_object_t msg,
+_mrcs_session_handle_record_cache_local_record_inquiry(OSStatus * const out_error)
+{
+	OSStatus err;
+	mdns_xpc_dictionary_t result = mdns_xpc_dictionary_create_empty();
+	require_action_quiet(result, exit, err = kNoResourcesErr);
+
+	err = _mrcs_server_record_cache_enumerate_local_records(
+	^(const char * const name, const uint8_t * const rdata, const uint16_t rdlen, const sockaddr_ip * const source_addr)
+	{
+		char *txt_str = NULL;
+		if (rdata) {
+			const OSStatus txt_str_err = DNSRecordDataToString(rdata, rdlen, kDNSRecordType_TXT, &txt_str);
+			if (!txt_str) {
+				os_log_fault(_mdns_server_log(),
+					"Failed to create device-info TXT RDATA string -- record name: '%{private}s', error: %{mdns:err}ld",
+					name, (long)txt_str_err);
+			}
+		}
+		const char *source_addr_str = NULL;
+		char str_buf[Max(INET_ADDRSTRLEN, INET6_ADDRSTRLEN)];
+		switch (source_addr->sa.sa_family) {
+			case AF_INET:
+				source_addr_str = inet_ntop(AF_INET, &source_addr->v4.sin_addr.s_addr, str_buf, sizeof(str_buf));
+				break;
+
+			case AF_INET6:
+				source_addr_str = inet_ntop(AF_INET6, source_addr->v6.sin6_addr.s6_addr, str_buf, sizeof(str_buf));
+				break;
+
+			default:
+				break;
+		}
+		mrc_xpc_cached_local_record_inquiry_result_add(result, name, txt_str, source_addr_str);
+		ForgetMem(&txt_str);
+	});
+	mdns_require_noerr_quiet(err, exit);
+
+exit:
+	mdns_assign(out_error, err);
+	return result;
+}
+
+//======================================================================================================================
+
+static OSStatus
+_mrcs_session_handle_record_cache_flush(__unused const mrcs_session_t me, const xpc_object_t msg)
+{
+	OSStatus err;
+	const xpc_object_t params = mrc_xpc_message_get_params(msg);
+	mdns_require_action_quiet(params, exit, err = kParamErr);
+
+	const char * const record_name = mrc_xpc_record_cache_flush_params_get_record_name(params);
+	mdns_require_action_quiet(record_name, exit, err = kParamErr);
+
+	bool have_key_tag = false;
+	const uint16_t key_tag = mrc_xpc_record_cache_flush_params_get_key_tag(params, &have_key_tag);
+	if (have_key_tag) {
+		err = _mrcs_server_record_cache_flush_by_name_and_key_tag(record_name, key_tag);
+		mdns_require_noerr_quiet(err, exit);
+	} else {
+		err = _mrcs_server_record_cache_flush_by_name(record_name);
+		mdns_require_noerr_quiet(err, exit);
+	}
+
+exit:
+	return err;
+}
+
+//======================================================================================================================
+
+static mdns_xpc_dictionary_t
+_mrcs_session_handle_command(const mrcs_session_t me, const char * const command_str, const xpc_object_t msg,
 	OSStatus * const out_error)
 {
 	OSStatus err;
-	mdns_xpc_dictionary_t result;
-	if (strcmp(command, g_mrc_command_dns_proxy_start) == 0) {
-		err = _mrcs_session_handle_dns_proxy_start(me, msg);
-		result = NULL;
-	} else if (strcmp(command, g_mrc_command_dns_proxy_stop) == 0) {
-		err = _mrcs_session_handle_dns_proxy_stop(me, msg);
-		result = NULL;
-	} else if (strcmp(command, g_mrc_command_dns_proxy_get_state) == 0) {
-		result = _mrcs_session_handle_dns_proxy_get_state(&err);
-	} else {
-		err = kCommandErr;
-		result = NULL;
+	mdns_xpc_dictionary_t result = NULL;
+	const mrcs_command_t command = _mrcs_server_command_string_to_code(command_str);
+	const char * const entitlement = _mrcs_server_get_required_entitlement(command);
+	mdns_require_action_quiet(entitlement, exit, err = kCommandErr);
+
+	const bool entitled = mdns_xpc_connection_is_entitled(me->connection, entitlement);
+	require_action_quiet(entitled, exit, err = kMissingEntitlementErr);
+
+	switch (command) {
+		case mrcs_command_dns_proxy_start:
+			err = _mrcs_session_handle_dns_proxy_start(me, msg);
+			break;
+
+		case mrcs_command_dns_proxy_stop:
+			err = _mrcs_session_handle_dns_proxy_stop(me, msg);
+			break;
+
+		case mrcs_command_dns_proxy_get_state:
+			result = _mrcs_session_handle_dns_proxy_get_state(&err);
+			break;
+
+		case mrcs_command_dns_service_registration_start:
+			err = _mrcs_session_handle_dns_service_registration_start(me, msg);
+			break;
+
+		case mrcs_command_dns_service_registration_stop:
+			err = _mrcs_session_handle_dns_service_registration_stop(me, msg);
+			break;
+
+		case mrcs_command_discovery_proxy_start:
+			err = _mrcs_session_handle_discovery_proxy_start(me, msg);
+			break;
+
+		case mrcs_command_discovery_proxy_stop:
+			err = _mrcs_session_handle_discovery_proxy_stop(me, msg);
+			break;
+
+		case mrcs_command_cached_local_record_inquiry:
+			result = _mrcs_session_handle_record_cache_local_record_inquiry(&err);
+			break;
+
+		case mrcs_command_record_cache_flush:
+			err = _mrcs_session_handle_record_cache_flush(me, msg);
+			break;
+
+		case mrcs_command_invalid:
+		MDNS_COVERED_SWITCH_DEFAULT:
+			err = kCommandErr;
+			break;
 	}
+
+exit:
 	if (out_error) {
 		*out_error = err;
 	}
@@ -422,9 +1086,6 @@
 {
 	OSStatus err;
 	mdns_xpc_dictionary_t result = NULL;
-	const bool entitled = mdns_xpc_connection_is_entitled(me->connection, "com.apple.mDNSResponder.dnsproxy");
-	require_action_quiet(entitled, exit, err = kMissingEntitlementErr);
-
 	const char * const command = mrc_xpc_message_get_command(msg);
 	require_action_quiet(command, exit, err = kCommandErr);
 
@@ -438,9 +1099,6 @@
 		xpc_connection_send_message(me->connection, reply);
 		xpc_forget(&reply);
 	}
-	if (!entitled) {
-		xpc_connection_cancel(me->connection);
-	}
 }
 
 //======================================================================================================================
@@ -489,6 +1147,48 @@
 }
 
 //======================================================================================================================
+// MARK: - DNS Service Registration Request Methods
+
+static char *
+_mrcs_dns_service_registration_request_copy_description(const mrcs_dns_service_registration_request_t me,
+	const bool debug, const bool privacy)
+{
+	char *description = NULL;
+	const mdns_description_options_t desp_opt = (privacy ? mdns_description_opt_privacy : mdns_description_opt_none);
+	mdns_string_builder_t sb = mdns_string_builder_create(0, NULL);
+	mdns_require_quiet(sb, exit);
+
+	OSStatus err;
+	if (debug) {
+		const mdns_kind_t kind = mrcs_get_kind(me);
+		err = mdns_string_builder_append_formatted(sb, "<%s: %p>: ", kind->name, (void *)me);
+		mdns_require_noerr_quiet(err, exit);
+	}
+	if (me->do53_definition) {
+		mdns_string_builder_append_description(sb, me->do53_definition, desp_opt);
+	} else if (me->push_definition) {
+		mdns_string_builder_append_description(sb, me->push_definition, desp_opt);
+	} else {
+		mdns_string_builder_append_formatted(sb, "<empty dns service definition>");
+	}
+	description = mdns_string_builder_copy_string(sb);
+	mdns_require_quiet(description, exit);
+
+exit:
+	mdns_forget(&sb);
+	return description;
+}
+
+//======================================================================================================================
+
+static void
+_mrcs_dns_service_registration_request_finalize(const mrcs_dns_service_registration_request_t me)
+{
+	mdns_forget(&me->do53_definition);
+	mdns_forget(&me->push_definition);
+}
+
+//======================================================================================================================
 // MARK: - Helpers
 
 static mrcs_dns_proxy_t
diff --git a/mDNSMacOSX/libmrc/src/mrcs_server.h b/mDNSMacOSX/libmrc/src/mrcs_server.h
index d02a93b..3fb9832 100644
--- a/mDNSMacOSX/libmrc/src/mrcs_server.h
+++ b/mDNSMacOSX/libmrc/src/mrcs_server.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021-2022 Apple Inc. All rights reserved.
+ * Copyright (c) 2021-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -21,6 +21,8 @@
 
 #include <MacTypes.h>
 #include <mdns/base.h>
+#include <mdns/dns_service.h>
+#include <Network/Network_Private.h>
 
 MDNS_ASSUME_NONNULL_BEGIN
 
@@ -66,29 +68,241 @@
 
 /*!
  *	@brief
- *		A structure containing handlers to be invoked an mDNSResponder control server.
+ *		A structure containing dns proxy handlers to be invoked by an mDNSResponder control server.
  */
-struct mrcs_server_handlers_s {
-	mrcs_server_dns_proxy_start_handler_f		dns_proxy_start;
-	mrcs_server_dns_proxy_stop_handler_f		dns_proxy_stop;
-	mrcs_server_dns_proxy_get_state_handler_f	dns_proxy_get_state;
+struct mrcs_server_dns_proxy_handlers_s {
+	mrcs_server_dns_proxy_start_handler_f		start;
+	mrcs_server_dns_proxy_stop_handler_f		stop;
+	mrcs_server_dns_proxy_get_state_handler_f	get_state;
 };
 
-typedef const struct mrcs_server_handlers_s *mrcs_server_handlers_t;
+typedef const struct mrcs_server_dns_proxy_handlers_s *mrcs_server_dns_proxy_handlers_t;
 
 /*!
  *	@brief
- *		Initializes mDNSResponder's control server.
+ *		A handler that starts a DNS service registration.
  *
- *	@param handlers
- *		The handlers to be used by the control server.
+ *	@param definition
+ *		The DNS service's definition.
+ *
+ *	@param connection_error_queue
+ *		Dispatch queue for the connection error event handler.
+ *
+ *	@param connection_error_handler
+ *		The connection error event handler.
  *
  *	@result
- *		kNoErr if the control server was successfully initialized. Otherwise, a non-zero error code to indicate
- *		why initialization failed.
+ *		A non-zero identifier for the DNS service if the registration was successful. Otherwise,
+ *		MDNS_DNS_SERVICE_INVALID_ID.
  */
-OSStatus
-mrcs_server_init(mrcs_server_handlers_t handlers);
+typedef mdns_dns_service_id_t
+(*mrcs_server_dns_service_registration_start_handler_f)(mdns_any_dns_service_definition_t definition,
+	dispatch_queue_t _Nullable connection_error_queue, mdns_event_handler_t _Nullable connection_error_handler);
+
+/*!
+ *	@brief
+ *		A handler that stops a DNS service registration.
+ *
+ *	@param ident
+ *		The identifier returned by a mrcs_server_dns_service_registration_start_handler_f handler when the DNS
+ *		service was registered.
+ */
+typedef void
+(*mrcs_server_dns_service_registration_stop_handler_f)(mdns_dns_service_id_t ident);
+
+/*!
+ *	@brief
+ *		A structure containing dns service registration handlers to be invoked by an mDNSResponder control
+ *		server.
+ */
+struct mrcs_server_dns_service_registration_handlers_s {
+	mrcs_server_dns_service_registration_start_handler_f	start;
+	mrcs_server_dns_service_registration_stop_handler_f		stop;
+};
+
+typedef const struct mrcs_server_dns_service_registration_handlers_s *mrcs_server_dns_service_registration_handlers_t;
+
+/*!
+ *	@brief
+ *		A handler that starts a specified discovery proxy.
+ *
+ *	@param ifindex
+ *		The interface index of the discovery proxy.
+ *
+ *	@param addresses
+ *		An array of mdns_address_t objects of the discovery proxy.
+ *
+ *	@param domains
+ *		An array of mdns_domain_name_t objects of the discovery proxy.
+ *
+ *	@param certificates
+ *		An array of CFDataRef TLS certificates in the DER representation of an X.509 certificate, which can be
+ *		used as trust anchor when doing certificate evaluation.
+ *
+ *	@result
+ *		kNoErr if the discovery proxy was successfully started. Otherwise, a non-zero error code to indicate why
+ *		the DNS proxy was not started.
+ */
+typedef OSStatus
+(*mrcs_server_discovery_proxy_start_handler_f)(uint32_t ifindex, CFArrayRef addresses, CFArrayRef domains,
+	CFArrayRef certificates);
+
+/*!
+ *	@brief
+ *		A handler that stops an existing discovery proxy.
+ *
+ *	@result
+ *		kNoErr if the discovery proxy was successfully stopped. Otherwise, a non-zero error code to indicate why
+ *		the discovery proxy was not stopped.
+ */
+typedef OSStatus
+(*mrcs_server_discovery_proxy_stop_handler_f)(void);
+
+/*!
+ *	@brief
+ *		A structure containing discovery proxy handlers to be invoked an mDNSResponder control server.
+ */
+struct mrcs_server_discovery_proxy_handlers_s {
+	mrcs_server_discovery_proxy_start_handler_f	start;
+	mrcs_server_discovery_proxy_stop_handler_f	stop;
+};
+
+typedef const struct mrcs_server_discovery_proxy_handlers_s *mrcs_server_discovery_proxy_handlers_t;
+
+/*!
+ *	@brief
+ *		Type of block that handles a single resource record name during an enumeration of resource record names.
+ *
+ *	@param name
+ *		The resource record name in presentation format as a C string.
+ *
+ *	@param txt_rdata
+ *		The record's RDATA if it's a TXT record that belongs to a subdomain of _device-info._tcp.local.
+ *
+ *	@param txt_rdlen
+ *		The record's RDATA length if it's a TXT record that belongs to a subdomain of _device-info._tcp.local.
+ *
+ *	@param source_address
+ *		The record's source IPv4 or IPv6 address, if available.
+ */
+typedef void
+(^mrcs_record_applier_t)(const char *name, const uint8_t * _Nullable txt_rdata, uint16_t txt_rdlen,
+	const sockaddr_ip *source_address);
+
+/*!
+ *	@brief
+ *		Enumerates the .local resource records from the latest snapshot of the resource record cache.
+ *
+ *	@param applier
+ *		Block to synchronously invoke once for each record.
+ *
+ *	@discussion
+ *		Currently, only the record names of .local resource records and the RDATA of *._device-info._tcp.local
+ *		TXT records is being divulged. Therefore, not every single .local resource record is enumerated. For
+ *		example, if multiple .local resource records have the same name, the applier is invoked only once with
+ *		the source address of one of the records.
+ *
+ *		If the record name is a subdomain of _device-info._tcp.local and at least one TXT record is present in
+ *		the cache, then one of the TXT record's RDATA and RDATA length are passed to the applier along with the
+ *		name of the record.
+ */
+typedef void
+(*mrcs_server_record_cache_enumerate_local_records_f)(mrcs_record_applier_t applier);
+
+/*!
+ *	@brief
+ *		Flushes all records that have a specified record name from the record cache.
+ *
+ *	@param record_name
+ *		The record name.
+ */
+typedef void
+(*mrcs_server_record_cache_flush_by_name_f)(const char *record_name);
+
+/*!
+ *	@brief
+ *		Flushes all records that have a specified record name and key tag from the record cache.
+ *
+ *	@param record_name
+ *		The record name.
+ *
+ *	@param key_tag
+ *		The key tag.
+ */
+typedef void
+(*mrcs_server_record_cache_flush_by_name_and_key_tag_f)(const char *record_name, uint16_t key_tag);
+
+/*!
+ *	@brief
+ *		A structure containing handlers to be invoked by an mDNSResponder control server to directly access
+ *		records from the record cache.
+ */
+struct mrcs_server_record_cache_handlers_s {
+	mrcs_server_record_cache_enumerate_local_records_f		enumerate_local_records;
+	mrcs_server_record_cache_flush_by_name_f				flush_by_name;
+	mrcs_server_record_cache_flush_by_name_and_key_tag_f	flush_by_name_and_key_tag;
+};
+
+typedef const struct mrcs_server_record_cache_handlers_s *mrcs_server_record_cache_handlers_t;
+
+/*!
+ *	@brief
+ *		Sets the mDNSResponder control server's DNS proxy handlers.
+ *
+ *	@param handlers
+ *		The DNS proxy handlers.
+ *
+ *	@discussion
+ *		This function has no effect after the server has been activated.
+ */
+void
+mrcs_server_set_dns_proxy_handlers(mrcs_server_dns_proxy_handlers_t handlers);
+
+/*!
+ *	@brief
+ *		Sets the mDNSResponder control server's DNS service registration handlers.
+ *
+ *	@param handlers
+ *		The DNS service registration handlers.
+ *
+ *	@discussion
+ *		This function has no effect after the server has been activated.
+ */
+void
+mrcs_server_set_dns_service_registration_handlers(mrcs_server_dns_service_registration_handlers_t handlers);
+
+/*!
+ *	@brief
+ *		Sets the mDNSResponder control server's discovery proxy handlers.
+ *
+ *	@param handlers
+ *		The discovery proxy handlers.
+ *
+ *	@discussion
+ *		This function has no effect after the server has been activated.
+ */
+void
+mrcs_server_set_discovery_proxy_handlers(mrcs_server_discovery_proxy_handlers_t handlers);
+
+/*!
+ *	@brief
+ *		Sets the mDNSResponder control server's record cache handlers.
+ *
+ *	@param handlers
+ *		The record cache handlers.
+ *
+ *	@discussion
+ *		This function has no effect after the server has been activated.
+ */
+void
+mrcs_server_set_record_cache_handlers(mrcs_server_record_cache_handlers_t handlers);
+
+/*!
+ *	@brief
+ *		Activates the mDNSResponder control server.
+ */
+void
+mrcs_server_activate(void);
 
 __END_DECLS
 
diff --git a/mDNSMacOSX/libmrc/src/mrcs_server_internal.h b/mDNSMacOSX/libmrc/src/mrcs_server_internal.h
index bc1b7ab..4d82e54 100644
--- a/mDNSMacOSX/libmrc/src/mrcs_server_internal.h
+++ b/mDNSMacOSX/libmrc/src/mrcs_server_internal.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 Apple Inc. All rights reserved.
+ * Copyright (c) 2021-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -23,5 +23,6 @@
 
 MRCS_DECL(session);
 MRCS_DECL(dns_proxy_request);
+MRCS_DECL(dns_service_registration_request);
 
 #endif	// MRCS_SERVER_INTERNAL_H
diff --git a/mDNSMacOSX/log/liblog_mdnsresponder.m b/mDNSMacOSX/log/liblog_mdnsresponder.m
index 9422274..ef2115f 100644
--- a/mDNSMacOSX/log/liblog_mdnsresponder.m
+++ b/mDNSMacOSX/log/liblog_mdnsresponder.m
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2019-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,21 +14,39 @@
  * limitations under the License.
  */
 
-#import <Foundation/Foundation.h>
-#import <arpa/inet.h>
-#import <os/log_private.h>
 #import <AssertMacros.h>
+#import <arpa/inet.h>
 #import <CoreUtils/CoreUtils.h>
+#import <DeviceToDeviceManager/DeviceToDeviceManager.h>
+#import <Foundation/Foundation.h>
 #import <mdns/DNSMessage.h>
+#import <mdns/general.h>
+#import "mDNSDebugShared.h"
+#import <net/net_kev.h>
+#import <os/log_private.h>
 #import "mdns_strict.h"
 
 #if !COMPILER_ARC
     #error "This file must be compiled with ARC."
 #endif
 
-// MDNS Mutable Attribute String
-#define MDNSAS(str) [[NSAttributedString alloc] initWithString:(str)]
-#define MDNSASWithFormat(format, ...) MDNSAS(([[NSString alloc] initWithFormat:format, ##__VA_ARGS__]))
+//======================================================================================================================
+// MARK: - Specifiers
+//
+//  Use                             Specifier                           Arguments                               Notes
+//  D2D service change event        %{mdnsresponder:d2d_service_event}d event (D2DServiceEvent)
+//  DNS scope type                  %{mdnsresponder:dns_scope_type}d    scope type (ScopeType)
+//  Domain name                     %{mdnsresponder:domain_name}.*P     length (int), pointer (void *)
+//  Domain label                    %{mdnsresponder:domain_label}.*P    length (int), pointer (void *)
+//  Hexadecimal bytes               %{mdnsresponder:hex_sequence}.*P    length (int), pointer (void *)
+//  IP address                      %{mdnsresponder:ip_addr}.20P        pointer (mDNSAddr *)
+//  Data-Link event                 %{mdnsresponder:kev_dl_event}d      event (uint32_t)
+//  MAC address                     %{mdnsresponder:mac_addr}.6P        pointer (mDNSEthAddr *)
+//  Name hash and type in packet    %{mdnsresponder:mdns_name_hash_type_bytes}  length (int), pointer (void *)
+//  Network change event flags      %{mdnsresponder:net_change_flags}d  flags (mDNSNetworkChangeEventFlags_t)
+
+//======================================================================================================================
+// MARK: - Data Structures
 
 // os_log(OS_LOG_DEFAULT, "IP Address(IPv4/IPv6): %{mdnsresponder:ip_addr}.20P", <the address of mDNSAddr structure>);
 typedef struct
@@ -41,6 +59,21 @@
     } ip;
 } mDNSAddrCompat;
 
+// Definition imported from mDNSEmbeddedAPI.h
+typedef enum
+{
+    kScopeNone         = 0, // DNS server used by unscoped questions.
+    kScopeInterfaceID  = 1, // Scoped DNS server used only by scoped questions.
+    kScopeServiceID    = 2  // Service specific DNS server used only by questions have a matching serviceID.
+} ScopeType;
+
+//======================================================================================================================
+// MARK: - Helpers
+
+// MDNS Mutable Attribute String
+#define MDNSAS(str) [[NSAttributedString alloc] initWithString:(str)]
+#define MDNSASWithFormat(format, ...) MDNSAS(([[NSString alloc] initWithFormat:format, ##__VA_ARGS__]))
+
 static NS_RETURNS_RETAINED NSAttributedString *
 MDNSOLCopyFormattedStringmDNSIPAddr(id value)
 {
@@ -76,6 +109,9 @@
     return nsa_str;
 }
 
+//======================================================================================================================
+// MARK: - Internal Functions
+
 // os_log(OS_LOG_DEFAULT, "MAC Address: %{mdnsresponder:mac_addr}.6P", <the address of 6-byte MAC address>);
 #define MAC_ADDRESS_LEN 6
 static NS_RETURNS_RETAINED NSAttributedString *
@@ -154,10 +190,9 @@
         nsa_str = MDNSASWithFormat(@"<failed to decode - invalid data type: %@>", [(NSObject *)value description]));
 
     data = (NSData *)value;
-    label_length = ((uint8_t *)data.bytes)[0];
-    require_action_quiet(data.bytes != NULL && data.length != 0, exit,
-        nsa_str = MDNSASWithFormat(@"failed to decoded - malformed domain label"));
+    mdns_require_action_quiet(data.bytes != NULL && data.length > 0, exit, nsa_str = MDNSAS(@"<NULL DOMAIN LABEL>"));
 
+    label_length = ((uint8_t *)data.bytes)[0];
     require_action_quiet((label_length <= kDomainLabelLengthMax) && (data.length == (1 + label_length)), exit,
         nsa_str = MDNSASWithFormat(@"failed to decode - invalid domain label length - "
             "data length: %lu, label length: %lu", (unsigned long)data.length, label_length));
@@ -226,13 +261,17 @@
     const size_t pair_bytes_len = sizeof(uint32_t) + sizeof(uint16_t);
 
     const char *sep = "";
-    for (size_t i = 0, count = length / pair_bytes_len; i < count; i++)
-    {
+    for (size_t i = 0, count = length / pair_bytes_len; i < count; i++) {
         const uint8_t *ptr = bytes + (pair_bytes_len * i);
         const uint32_t nameHash = ReadBig32(ptr);
         ptr += 4;
         const uint16_t type = ReadBig16(ptr);
-        [nsstr appendFormat:@"%s(%x %s)", sep, nameHash, DNSRecordTypeValueToString(type)];
+        const char * const type_str = DNSRecordTypeValueToString(type);
+        if (type_str) {
+            [nsstr appendFormat:@"%s(%x %s)", sep, nameHash, type_str];
+        } else {
+            [nsstr appendFormat:@"%s(%x TYPE%u)", sep, nameHash, type];
+        }
         sep = " ";
     }
     nsa_str = MDNSAS(nsstr);
@@ -246,18 +285,188 @@
     NS_RETURNS_RETAINED NSAttributedString *(*function)(id);
 };
 
+//======================================================================================================================
+
+static NS_RETURNS_RETAINED NSAttributedString *
+MDNSOLCopyFormattedStringD2DServiceEvent(const id value)
+{
+    NSString *nsstr;
+    const NSNumber *number;
+    mdns_require_action_quiet([(NSObject *)value isKindOfClass:[NSNumber class]], exit, nsstr = nil);
+
+    number = (const NSNumber *)value;
+    switch ([number longLongValue])
+    {
+    #define CASE_TO_NSSTR(s) case s: nsstr = @(#s); break
+        CASE_TO_NSSTR(D2DServiceFound);
+        CASE_TO_NSSTR(D2DServiceLost);
+        CASE_TO_NSSTR(D2DServiceResolved);
+        CASE_TO_NSSTR(D2DServiceRetained);
+        CASE_TO_NSSTR(D2DServiceReleased);
+        CASE_TO_NSSTR(D2DServicePeerLost);
+        default:
+            nsstr = nil;
+            break;
+    #undef CASE_TO_NSSTR
+    }
+
+exit:
+    return MDNSAS(nsstr);
+}
+
+//======================================================================================================================
+
+static NS_RETURNS_RETAINED NSAttributedString *
+MDNSOLCopyFormattedStringDNSScopeType(const id value)
+{
+    NSString *nsstr;
+    const NSNumber *number;
+    mdns_require_action_quiet([(NSObject *)value isKindOfClass:[NSNumber class]], exit, nsstr = nil);
+
+    number = (const NSNumber *)value;
+    switch ([number longLongValue])
+    {
+        case kScopeNone:
+            nsstr = @"Unscoped";
+            break;
+        case kScopeInterfaceID:
+            nsstr = @"Interface scoped";
+            break;
+        case kScopeServiceID:
+            nsstr = @"Service scoped";
+            break;
+        default:
+            nsstr = nil;
+            break;
+    }
+
+exit:
+    return MDNSAS(nsstr);
+}
+
+//======================================================================================================================
+
+typedef struct MDNSNetworkChangeEventFlagDescription_s MDNSNetworkChangeEventFlagDescription_t;
+struct MDNSNetworkChangeEventFlagDescription_s {
+    const char *                    desc;
+    mDNSNetworkChangeEventFlags_t   flags;
+};
+
+static NS_RETURNS_RETAINED NSAttributedString *
+MDNSOLCopyFormattedStringNetworkChangeEventFlag(const id value)
+{
+    NSMutableString *nsstr = [[NSMutableString alloc] initWithCapacity:0];
+    const NSNumber *number;
+    mdns_require_quiet([(NSObject *)value isKindOfClass:[NSNumber class]], exit);
+
+    number = (const NSNumber *)value;
+    const unsigned long long opts = [number unsignedLongLongValue];
+
+#define _item(FLG, DESC) {.flags = (FLG), .desc = (DESC)}
+    const MDNSNetworkChangeEventFlagDescription_t descriptions[] = {
+        _item(mDNSNetworkChangeEventFlag_LocalHostname,  "local-hostname"),
+        _item(mDNSNetworkChangeEventFlag_ComputerName,   "computer-name"),
+        _item(mDNSNetworkChangeEventFlag_DNS,            "dns"),
+        _item(mDNSNetworkChangeEventFlag_DynamicDNS,     "dynamic-dns/set-key-chain-timer"),
+        _item(mDNSNetworkChangeEventFlag_IPv4LL,         "service-configured-for-v4-linklocal"),
+        _item(mDNSNetworkChangeEventFlag_P2PLike,        "P2P/IFEF_DIRECTLINK/IFEF_AWDL/car-play"),
+    };
+#undef _item
+    const char *prefix = "";
+    [nsstr appendFormat:@"0x%llX {", opts];
+    for (size_t i = 0; i < countof(descriptions); i++) {
+        const MDNSNetworkChangeEventFlagDescription_t * const desc = &descriptions[i];
+        if (opts & desc->flags) {
+            [nsstr appendFormat:@"%s%s", prefix, desc->desc];
+            prefix = ", ";
+        }
+    }
+    [nsstr appendString:@"}"];
+
+exit:
+    return MDNSAS(nsstr);
+}
+
+//======================================================================================================================
+
+typedef struct KEVDataLinkEventDescription_s KEVDataLinkEventDescription_t;
+struct KEVDataLinkEventDescription_s {
+    const char *    desc;
+    uint32_t        event;
+};
+
+static NS_RETURNS_RETAINED NSAttributedString *
+MDNSOLCopyFormattedStringDataLinkEvent(const id value)
+{
+    NSString *nsstr = @"";
+    const NSNumber *number;
+    mdns_require_quiet([(NSObject *)value isKindOfClass:[NSNumber class]], exit);
+
+    number = (const NSNumber *)value;
+    const unsigned long long event = [number unsignedLongLongValue];
+    mdns_require_quiet(event >= KEV_DL_SIFFLAGS && event <= KEV_DL_LOW_POWER_MODE_CHANGED, exit);
+
+    const uint32_t dl_event = (uint32_t)event;
+    switch (dl_event) {
+    #define CASE_TO_STR(EVENT) case EVENT: nsstr = @(#EVENT); break
+        CASE_TO_STR(KEV_DL_SIFFLAGS);
+        CASE_TO_STR(KEV_DL_SIFMETRICS);
+        CASE_TO_STR(KEV_DL_SIFMTU);
+        CASE_TO_STR(KEV_DL_SIFPHYS);
+        CASE_TO_STR(KEV_DL_SIFMEDIA);
+        CASE_TO_STR(KEV_DL_SIFGENERIC);
+        CASE_TO_STR(KEV_DL_ADDMULTI);
+        CASE_TO_STR(KEV_DL_DELMULTI);
+        CASE_TO_STR(KEV_DL_IF_ATTACHED);
+        CASE_TO_STR(KEV_DL_IF_DETACHING);
+        CASE_TO_STR(KEV_DL_IF_DETACHED);
+        CASE_TO_STR(KEV_DL_LINK_OFF);
+        CASE_TO_STR(KEV_DL_LINK_ON);
+        CASE_TO_STR(KEV_DL_PROTO_ATTACHED);
+        CASE_TO_STR(KEV_DL_PROTO_DETACHED);
+        CASE_TO_STR(KEV_DL_LINK_ADDRESS_CHANGED);
+        CASE_TO_STR(KEV_DL_WAKEFLAGS_CHANGED);
+        CASE_TO_STR(KEV_DL_IF_IDLE_ROUTE_REFCNT);
+        CASE_TO_STR(KEV_DL_IFCAP_CHANGED);
+        CASE_TO_STR(KEV_DL_LINK_QUALITY_METRIC_CHANGED);
+        CASE_TO_STR(KEV_DL_NODE_PRESENCE);
+        CASE_TO_STR(KEV_DL_NODE_ABSENCE);
+        CASE_TO_STR(KEV_DL_PRIMARY_ELECTED);
+        CASE_TO_STR(KEV_DL_ISSUES);
+        CASE_TO_STR(KEV_DL_IFDELEGATE_CHANGED);
+        CASE_TO_STR(KEV_DL_AWDL_UNRESTRICTED);
+        CASE_TO_STR(KEV_DL_RRC_STATE_CHANGED);
+        CASE_TO_STR(KEV_DL_QOS_MODE_CHANGED);
+        CASE_TO_STR(KEV_DL_LOW_POWER_MODE_CHANGED);
+        default:
+            nsstr = @"<Unknown Data-Link event>";
+            break;
+    #undef CASE_TO_STR
+    }
+
+exit:
+    return MDNSAS(nsstr);
+}
+
+//======================================================================================================================
+// MARK: - External Functions
+
 NS_RETURNS_RETAINED
 NSAttributedString *
 OSLogCopyFormattedString(const char *type, id value, __unused os_log_type_info_t info)
 {
     NSAttributedString *nsa_str = nil;
     static const struct MDNSOLFormatters formatters[] = {
-        { .type = "ip_addr",                    .function = MDNSOLCopyFormattedStringmDNSIPAddr },
-        { .type = "mac_addr",                   .function = MDNSOLCopyFormattedStringmDNSMACAddr },
+        { .type = "d2d_service_event",          .function = MDNSOLCopyFormattedStringD2DServiceEvent },
+        { .type = "dns_scope_type",             .function = MDNSOLCopyFormattedStringDNSScopeType },
         { .type = "domain_name",                .function = MDNSOLCopyFormattedStringmDNSLabelSequenceName },
-        { .type = "domain_label",               .function = MDNSOLCopyFormattedStringmDNSLabel},
-        { .type = "hex_sequence",               .function = MDNSOLCopyFormattedStringHexSequence},
-        { .type = "mdns_name_hash_type_bytes",  .function = MDNSOLCopyFormattedStringMDNSNameHashTypeBytes},
+        { .type = "domain_label",               .function = MDNSOLCopyFormattedStringmDNSLabel },
+        { .type = "hex_sequence",               .function = MDNSOLCopyFormattedStringHexSequence },
+        { .type = "ip_addr",                    .function = MDNSOLCopyFormattedStringmDNSIPAddr },
+        { .type = "kev_dl_event",               .function = MDNSOLCopyFormattedStringDataLinkEvent },
+        { .type = "mac_addr",                   .function = MDNSOLCopyFormattedStringmDNSMACAddr },
+        { .type = "mdns_name_hash_type_bytes",  .function = MDNSOLCopyFormattedStringMDNSNameHashTypeBytes },
+        { .type = "net_change_flags",           .function = MDNSOLCopyFormattedStringNetworkChangeEventFlag },
     };
 
     for (int i = 0; i < (int)(sizeof(formatters) / sizeof(formatters[0])); i++) {
diff --git a/mDNSMacOSX/mDNSDebugShared.h b/mDNSMacOSX/mDNSDebugShared.h
new file mode 100644
index 0000000..59a5899
--- /dev/null
+++ b/mDNSMacOSX/mDNSDebugShared.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (c) 2024 Apple Inc. All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef MDNS_DEBUG_SHARED_H
+#define MDNS_DEBUG_SHARED_H
+
+#include <mdns/general.h>
+
+MDNS_CLOSED_OPTIONS(mDNSNetworkChangeEventFlags_t, uint32_t,
+	mDNSNetworkChangeEventFlag_None				= 0,
+	mDNSNetworkChangeEventFlag_LocalHostname	= (1U << 0),
+	mDNSNetworkChangeEventFlag_ComputerName		= (1U << 1),
+	mDNSNetworkChangeEventFlag_DNS				= (1U << 2),
+	mDNSNetworkChangeEventFlag_DynamicDNS		= (1U << 3),
+	mDNSNetworkChangeEventFlag_IPv4LL			= (1U << 4),
+	mDNSNetworkChangeEventFlag_P2PLike			= (1U << 5),
+);
+
+#endif	// MDNS_DEBUG_SHARED_H
diff --git a/mDNSMacOSX/mDNSMacOSX.c b/mDNSMacOSX/mDNSMacOSX.c
index 9f7ffc4..d486132 100644
--- a/mDNSMacOSX/mDNSMacOSX.c
+++ b/mDNSMacOSX/mDNSMacOSX.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4; c-file-style: "bsd"; c-basic-offset: 4; fill-column: 108; indent-tabs-mode: nil; -*-
  *
- * Copyright (c) 2002-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -45,6 +45,7 @@
 #endif
 
 #include <mdns/power.h>
+#include <mdns/sockaddr.h>
 #include <mdns/tcpinfo.h>
 #include <stdio.h>
 #include <stdarg.h>                 // For va_list support
@@ -97,7 +98,6 @@
 #include <SystemConfiguration/SCPrivate.h>
 
 #include <Security/oidsalg.h> // To include the deprecated symbol `CSSMOID_APPLE_X509_BASIC`.
-#include "system_utilities.h"
 
 // Include definition of opaque_presence_indication for KEV_DL_NODE_PRESENCE handling logic.
 #include <Kernel/IOKit/apple80211/apple80211_var.h>
@@ -113,6 +113,7 @@
 #endif
 
 
+#include "mrcs_server.h"
 #include "mdns_strict.h"
 
 #define mDNS_IOREG_KEY               "mDNS_KEY"
@@ -322,10 +323,7 @@
         case kmDNSDebugState:
             sckey = CFSTR("State:/Network/mDNSResponder/DebugState");
             break;
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wcovered-switch-default"
-        default:
-#pragma clang diagnostic pop
+        MDNS_COVERED_SWITCH_DEFAULT:
             LogMsg("unrecognized key %d", key);
             goto fin;
     }
@@ -2243,6 +2241,24 @@
             LogInfo("SetupSocket: SO_NOWAKEFROMSLEEP failed %s", strerror(errno));
     }
 
+    // Attribute mDNS traffic to the com.apple.datausage.dns.multicast pseduo-identifier to distinguish it from
+    // other network traffic attributed to mDNSResponder.
+    if (mDNSSameIPPort(port, MulticastDNSPort))
+    {
+        // The UUID for com.apple.datausage.dns.multicast is 979C0A62-49FE-4739-BDCB-CAC584AC832D.
+        const mDNSu8 mDNSMulticastDataUsageUUID[UUID_SIZE] = {
+            0x97, 0x9C, 0x0A, 0x62, 0x49, 0xFE, 0x47, 0x39, 0xBD, 0xCB, 0xCA, 0xC5, 0x84, 0xAC, 0x83, 0x2D
+        };
+        err = setsockopt(skt, SOL_SOCKET, SO_DELEGATED_UUID, mDNSMulticastDataUsageUUID, sizeof(mDNSMulticastDataUsageUUID));
+        if (err != 0)
+        {
+            saved_errno = errno;
+            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_ERROR,
+                "SetupSocket: Attributing mDNS traffic to com.apple.datausage.dns.multicast failed: " PUB_S,
+                strerror(saved_errno));
+        }
+    }
+
     if (sa_family == AF_INET)
     {
         // We want to receive destination addresses
@@ -2718,7 +2734,6 @@
     io_service_t service = IOServiceGetMatchingService(kIOMainPortDefault, IOBSDNameMatching(kIOMainPortDefault, 0, intf->ifname));
     if (!service)
     {
-        LogRedact(MDNS_LOG_CATEGORY_SPS, MDNS_LOG_DEFAULT, "CheckInterfaceSupport: No service for interface " PUB_S , intf->ifname);
         return mDNSfalse;
     }
     mDNSBool    ret    = mDNSfalse;
@@ -2731,8 +2746,6 @@
     {
         io_name_t n1;
         IOObjectGetClass(service, n1);
-        LogRedact(MDNS_LOG_CATEGORY_SPS, MDNS_LOG_DEFAULT, "CheckInterfaceSupport: No " PUB_S " for interface " PUB_S "/" PUB_S " kr 0x%X",
-            key, intf->ifname, n1, kr);
         ret = mDNSfalse;
     }
 
@@ -2778,12 +2791,20 @@
     // when the power source is not AC Power.
     if (InterfaceSupportsKeepAlive(&i->ifinfo))
     {
-        LogSPS("NetWakeInterface: %s supports TCP Keepalive returning true", i->ifinfo.ifname);
+        LogRedact(MDNS_LOG_CATEGORY_SPS, MDNS_LOG_DEFAULT,
+            "NetWakeInterface: interface supports TCP Keepalive -- ifname: " PUB_S, i->ifinfo.ifname);
         return mDNStrue;
     }
 
     int s = socket(AF_INET, SOCK_DGRAM, 0);
-    if (s < 0) { LogMsg("NetWakeInterface socket failed %s error %d errno %d (%s)", i->ifinfo.ifname, s, errno, strerror(errno)); return(mDNSfalse); }
+    if (s < 0)
+    {
+        const int socket_errno = errno;
+        LogRedact(MDNS_LOG_CATEGORY_SPS, MDNS_LOG_ERROR,
+            "NetWakeInterface: socket failed -- socket: %d, ifname: " PUB_S ", error: %{darwin.errno}d", s,
+            i->ifinfo.ifname, socket_errno);
+        return mDNSfalse;
+    }
 
     struct ifreq ifr;
     mdns_strlcpy(ifr.ifr_name, i->ifinfo.ifname, sizeof(ifr.ifr_name));
@@ -2795,7 +2816,11 @@
         // error code is being returned from the kernel, we need to use the kernel version.
         #define KERNEL_EOPNOTSUPP 102
         if (ioctl_errno != KERNEL_EOPNOTSUPP) // "Operation not supported on socket", the expected result on Leopard and earlier
-            LogMsg("NetWakeInterface SIOCGIFWAKEFLAGS %s errno %d (%s)", i->ifinfo.ifname, ioctl_errno, strerror(ioctl_errno));
+        {
+            LogRedact(MDNS_LOG_CATEGORY_SPS, MDNS_LOG_ERROR,
+                "NetWakeInterface: SIOCGIFWAKEFLAGS failed -- ifname: " PUB_S ", error: %{darwin.errno}d",
+                i->ifinfo.ifname, ioctl_errno);
+        }
         // If on Leopard or earlier, we get EOPNOTSUPP, so in that case
         // we enable WOL if this interface is not AirPort and "Wake for Network access" is turned on.
         ifr.ifr_wake_flags = (ioctl_errno == KERNEL_EOPNOTSUPP && !(i)->BSSID.l[0] && i->m->SystemWakeOnLANEnabled) ? IF_WAKE_ON_MAGIC_PACKET : 0;
@@ -2806,8 +2831,8 @@
     // ifr.ifr_wake_flags = IF_WAKE_ON_MAGIC_PACKET;    // For testing with MacBook Air, using a USB dongle that doesn't actually support Wake-On-LAN
 
     LogRedact(MDNS_LOG_CATEGORY_SPS, MDNS_LOG_DEFAULT,
-        "NetWakeInterface: " PUB_S " " PRI_IP_ADDR " " PUB_S " WOMP",
-        i->ifinfo.ifname, &i->ifinfo.ip, (ifr.ifr_wake_flags & IF_WAKE_ON_MAGIC_PACKET) ? "supports" : "no");
+        "NetWakeInterface: interface -- ifname: " PUB_S ", address: " PRI_IP_ADDR ", supports Wake-On-Lan: " PUB_BOOL,
+        i->ifinfo.ifname, &i->ifinfo.ip, BOOL_PARAM((ifr.ifr_wake_flags & IF_WAKE_ON_MAGIC_PACKET) != 0));
 
     return((ifr.ifr_wake_flags & IF_WAKE_ON_MAGIC_PACKET) != 0);
 #endif  // TARGET_OS_WATCH
@@ -2847,14 +2872,14 @@
     // until rdar://problem/47933782 is addressed.
     if (strncmp(ifa_name, "llw", 3) == 0)
     {
-        LogMsg("isExcludedInterface: excluding %s", ifa_name);
+        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEBUG, "isExcludedInterface: excluding " PUB_S, ifa_name);
         return mDNStrue;
     }
 
     // Coprocessor interfaces are also excluded.
     if (sockFD < 0)
     {
-        LogMsg("isExcludedInterface: invalid socket FD passed: %d", sockFD);
+        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEBUG, "isExcludedInterface: invalid socket FD passed: %d", sockFD);
         return mDNSfalse;
     }
 
@@ -2863,13 +2888,16 @@
 
     if (ioctl(sockFD, SIOCGIFFUNCTIONALTYPE, (caddr_t)&ifr) == -1)
     {
-        LogMsg("isExcludedInterface: SIOCGIFFUNCTIONALTYPE failed, errno = %d (%s)", errno, strerror(errno));
+        const int socket_errno = errno;
+        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEBUG,
+            "isExcludedInterface: SIOCGIFFUNCTIONALTYPE failed -- error: %{darwin.errno}d", socket_errno);
         return mDNSfalse;
     }
 
     if (ifr.ifr_functional_type == IFRTYPE_FUNCTIONAL_INTCOPROC)
     {
-        LogMsg("isExcludedInterface: excluding coprocessor interface %s", ifa_name);
+        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEBUG,
+            "isExcludedInterface: excluding coprocessor interface " PUB_S, ifa_name);
         return mDNStrue;
     }
     else
@@ -3138,7 +3166,9 @@
     if (InfoSocket < 3 && errno != EAFNOSUPPORT)
         LogMsg("UpdateInterfaceList: InfoSocket error %d errno %d (%s)", InfoSocket, errno, strerror(errno));
 
+#if !MDNSRESPONDER_SUPPORTS(APPLE, KEEP_INTERFACES_DURING_SLEEP)
     if (m->SleepState == SleepState_Sleeping) ifa = NULL;
+#endif
 
     for (; ifa; ifa = ifa->ifa_next)
     {
@@ -3251,24 +3281,50 @@
                 }
                 else
                 {
+                    mDNSBool addInterface = mDNStrue;
+                    const sa_family_t family = ifa->ifa_addr->sa_family;
                     // Make sure ifa_netmask->sa_family is set correctly
                     // <rdar://problem/5492035> getifaddrs is returning invalid netmask family for fw0 and vmnet
-                    ifa->ifa_netmask->sa_family = ifa->ifa_addr->sa_family;
-                    int ifru_flags6 = 0;
-
-                    struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ifa->ifa_addr;
-                    if (ifa->ifa_addr->sa_family == AF_INET6 && InfoSocket >= 0)
+                    ifa->ifa_netmask->sa_family = family;
+                    switch (family)
                     {
-                        struct in6_ifreq ifr6;
-                        mDNSPlatformMemZero((char *)&ifr6, sizeof(ifr6));
-                        mdns_strlcpy(ifr6.ifr_name, ifa->ifa_name, sizeof(ifr6.ifr_name));
-                        ifr6.ifr_addr = *sin6;
-                        if (ioctl(InfoSocket, SIOCGIFAFLAG_IN6, &ifr6) != -1)
-                            ifru_flags6 = ifr6.ifr_ifru.ifru_flags6;
-                        verbosedebugf("%s %.16a %04X %04X", ifa->ifa_name, &sin6->sin6_addr, ifa->ifa_flags, ifru_flags6);
+                        case AF_INET:
+                        {
+                            struct sockaddr_in *const netmask = (struct sockaddr_in *)ifa->ifa_netmask;
+                            // If an IPv4 address has an all-ones netmask, then it's on a /32 subnet with exactly
+                            // one IPv4 address. It generally doesn't make sense to use such an IPv4 address for
+                            // mDNS. These type of IPv4 addresses are usually special-purpose. For example, IPv4
+                            // addresses used for 464XLAT have an all-ones netmask.
+                            if (netmask->sin_addr.s_addr == 0xFFFFFFFFU)
+                            {
+                                addInterface = mDNSfalse;
+                            }
+                            break;
+                        }
+                        case AF_INET6:
+                        {
+                            int ifru_flags6 = 0;
+                            struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ifa->ifa_addr;
+                            if (InfoSocket >= 0)
+                            {
+                                struct in6_ifreq ifr6;
+                                mDNSPlatformMemZero((char *)&ifr6, sizeof(ifr6));
+                                mdns_strlcpy(ifr6.ifr_name, ifa->ifa_name, sizeof(ifr6.ifr_name));
+                                ifr6.ifr_addr = *sin6;
+                                if (ioctl(InfoSocket, SIOCGIFAFLAG_IN6, &ifr6) != -1)
+                                    ifru_flags6 = ifr6.ifr_ifru.ifru_flags6;
+                                verbosedebugf("%s %.16a %04X %04X", ifa->ifa_name, &sin6->sin6_addr, ifa->ifa_flags, ifru_flags6);
+                            }
+                            if (ifru_flags6 & (IN6_IFF_TENTATIVE | IN6_IFF_DETACHED | IN6_IFF_DEPRECATED | IN6_IFF_TEMPORARY))
+                            {
+                                addInterface = mDNSfalse;
+                            }
+                            break;
+                        }
+                        default:
+                            break;
                     }
-
-                    if (!(ifru_flags6 & (IN6_IFF_TENTATIVE | IN6_IFF_DETACHED | IN6_IFF_DEPRECATED | IN6_IFF_TEMPORARY)))
+                    if (addInterface)
                     {
                         AddInterfaceToList(ifa, utc);
                     }
@@ -3415,7 +3471,8 @@
 
             if (i->Registered && i->Registered != primary)  // Sanity check
             {
-                LogMsg("SetupActiveInterfaces ERROR! n->Registered %p != primary %p", i->Registered, primary);
+                LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_ERROR,
+                    "SetupActiveInterfaces ERROR! n->Registered %p != primary %p", i->Registered, primary);
                 i->Registered = mDNSNULL;
             }
 
@@ -3442,7 +3499,8 @@
                 if ((strncmp(i->ifinfo.ifname, "p2p", 3) == 0) || i->ifinfo.DirectLink)
                 {
                     activationSpeed = FastActivation;
-                    LogInfo("SetupActiveInterfaces: %s DirectLink interface registering", i->ifinfo.ifname);
+                    LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_INFO,
+                        "SetupActiveInterfaces: " PUB_S " DirectLink interface registering", i->ifinfo.ifname);
                 }
 #if MDNSRESPONDER_SUPPORTS(APPLE, SLOW_ACTIVATION)
                 else if (i->Flashing && i->Occulting)
@@ -3458,7 +3516,7 @@
                 mDNS_RegisterInterface(m, n, activationSpeed);
 
                 if (!mDNSAddressIsLinkLocal(&n->ip)) count++;
-                LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
+                LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
                         "SetupActiveInterfaces: Registered " PUB_S " (%u) BSSID " PRI_MAC_ADDR " Struct addr %p, primary %p,"
                         " " PRI_IP_ADDR "/%d" PUB_S PUB_S PUB_S,
                         i->ifinfo.ifname, i->scope_id, &i->BSSID, i, primary, &n->ip, CountMaskBits(&n->mask),
@@ -3530,13 +3588,12 @@
                 InterfaceActivationSpeed activationSpeed;
 
                 i->Flashing = !(i->ifa_flags & IFF_LOOPBACK) && (utc - i->AppearanceTime < 60);
-                LogRedact(MDNS_LOG_CATEGORY_NAT, MDNS_LOG_DEFAULT, "ClearInactiveInterfaces: Deregistering " PUB_S "(%u) " PRI_MAC_ADDR
-                    " InterfaceID %p(%p), primary %p, " PRI_IP_ADDR "/%d" PUB_S PUB_S PUB_S,
-                    i->ifinfo.ifname, i->scope_id, &i->BSSID, i->ifinfo.InterfaceID, i, primary,
-                    &i->ifinfo.ip, CountMaskBits(&i->ifinfo.mask),
-                    i->Flashing               ? " (Flashing)"  : "",
-                    i->Occulting              ? " (Occulting)" : "",
-                    i->ifinfo.InterfaceActive ? " (Primary)"   : "");
+                LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
+                    "ClearInactiveInterfaces: Deregistering " PUB_S "(%u) " PRI_MAC_ADDR
+                    " InterfaceID %p(%p), primary %p, " PRI_IP_ADDR "/%d -- "
+                    "flashing: " PUB_BOOL ", occulting: " PUB_BOOL ", primary: " PUB_BOOL,
+                    i->ifinfo.ifname, i->scope_id, &i->BSSID, i->ifinfo.InterfaceID, i, primary, &i->ifinfo.ip,
+                    CountMaskBits(&i->ifinfo.mask), i->Flashing, i->Occulting, i->ifinfo.InterfaceActive);
 
                 // "p2p*" interfaces used for legacy AirDrop reuse the scope-id, MAC address and the IP address
                 // every time it creates a new interface. We think it is a duplicate and hence consider it
@@ -3547,7 +3604,8 @@
                 if ((strncmp(i->ifinfo.ifname, "p2p", 3) == 0) || i->ifinfo.DirectLink)
                 {
                     activationSpeed = FastActivation;
-                    LogRedact(MDNS_LOG_CATEGORY_NAT, MDNS_LOG_DEFAULT, "ClearInactiveInterfaces: " PUB_S " DirectLink interface deregistering", i->ifinfo.ifname);
+                    LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
+                        "ClearInactiveInterfaces: " PUB_S " DirectLink interface deregistering", i->ifinfo.ifname);
                 }
 #if MDNSRESPONDER_SUPPORTS(APPLE, SLOW_ACTIVATION)
                 else if (i->Flashing && i->Occulting)
@@ -3584,11 +3642,21 @@
         {
             if (i->LastSeen == utc) i->LastSeen = utc - 1;
             const mDNSBool delete = ((utc - i->LastSeen) >= 60) ? mDNStrue : mDNSfalse;
-            LogRedact(MDNS_LOG_CATEGORY_NAT, MDNS_LOG_DEFAULT, "ClearInactiveInterfaces: " PUB_S " " PUB_S "(%u) " PRI_MAC_ADDR " InterfaceID %p(%p) " PRI_IP_ADDR
-                "/%d Age %d" PUB_S,
-                delete ? "Deleting" : "Holding", i->ifinfo.ifname, i->scope_id, &i->BSSID, i->ifinfo.InterfaceID, i,
-                &i->ifinfo.ip, CountMaskBits(&i->ifinfo.mask), utc - i->LastSeen,
-                i->ifinfo.InterfaceActive ? " (Primary)" : "");
+            if (delete)
+            {
+                LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
+                    "ClearInactiveInterfaces: Deleting " PUB_S "(%u) " PRI_MAC_ADDR " InterfaceID %p(%p) " PRI_IP_ADDR
+                    "/%d Age %d -- primary: " PUB_BOOL, i->ifinfo.ifname, i->scope_id, &i->BSSID, i->ifinfo.InterfaceID,
+                    i, &i->ifinfo.ip, CountMaskBits(&i->ifinfo.mask), utc - i->LastSeen, i->ifinfo.InterfaceActive);
+            }
+            else
+            {
+                LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
+                    "ClearInactiveInterfaces: Holding " PUB_S "(%u) " PRI_MAC_ADDR " InterfaceID %p(%p) " PRI_IP_ADDR
+                    "/%d Age %d -- primary: " PUB_BOOL, i->ifinfo.ifname, i->scope_id, &i->BSSID, i->ifinfo.InterfaceID,
+                    i, &i->ifinfo.ip, CountMaskBits(&i->ifinfo.mask), utc - i->LastSeen, i->ifinfo.InterfaceActive);
+            }
+
             if (delete)
             {
                 *p = i->next;
@@ -3654,9 +3722,11 @@
     scopeid = mDNSPlatformInterfaceIndexfromInterfaceID(m, InterfaceID, mDNStrue);
     // mDNS_snprintf always null terminates
     if (mDNS_snprintf(ifid_buf, sizeof(ifid_buf), "%u", scopeid) >= sizeof(ifid_buf))
-        LogMsg("UpdateSearchDomainHash: mDNS_snprintf failed for scopeid %u", scopeid);
-
-    LogInfo("UpdateSearchDomainHash: buf %s, ifid_buf %s", buf, ifid_buf);
+    {
+        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEBUG,
+            "UpdateSearchDomainHash: mDNS_snprintf failed for scopeid %u", scopeid);
+    }
+    LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEBUG, "UpdateSearchDomainHash: buf %s, ifid_buf %s", buf, ifid_buf);
     MD5_Update(sdc, buf, strlen(buf) + 1);
     MD5_Update(sdc, ifid_buf, strlen(ifid_buf) + 1);
 }
@@ -3673,46 +3743,65 @@
         // If the hash is different, either the search domains have changed or
         // the ordering between them has changed. Restart the questions that
         // would be affected by this.
-        LogInfo("FinalizeSearchDomains: The hash is different");
         memcpy(m->SearchDomainsHash, md5_hash, MD5_LEN);
         RetrySearchDomainQuestions(m);
     }
-    else { LogInfo("FinalizeSearchDomains: The hash is same"); }
 }
 
 mDNSlocal void ConfigSearchDomains(dns_resolver_t *resolver, mDNSInterfaceID interfaceId, mDNSu32 scope,  MD5_CTX *sdc, uint64_t generation)
 {
-    const char *scopeString = DNSScopeToString(scope);
     int j;
     domainname d;
 
     if (scope == kScopeNone)
         interfaceId = mDNSInterface_Any;
 
+#define LogRedactWithIfID(CATEGORY, LEVEL, ifID, FORMAT, ...)                       \
+    do {                                                                            \
+        if ((ifID) == mDNSInterface_Any)                                            \
+        {                                                                           \
+            LogRedact(CATEGORY, LEVEL, FORMAT, ##__VA_ARGS__);                      \
+        } else {                                                                    \
+            LogRedact(CATEGORY, LEVEL, FORMAT ", ifname: " PUB_S, ##__VA_ARGS__,    \
+                InterfaceNameForID(&mDNSStorage, (ifID)));                          \
+        }                                                                           \
+    } while(0)
+
     if (scope == kScopeNone || scope == kScopeInterfaceID)
     {
+        if (resolver->n_search > 0)
+        {
+            LogRedactWithIfID(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT, interfaceId,
+                "ConfigSearchDomains: configuring search domains -- "
+                "count: %d, scope type: " PUB_DNS_SCOPE_TYPE ", generation: %llu", resolver->n_search, scope,
+                generation);
+        }
+
         for (j = 0; j < resolver->n_search; j++)
         {
             if (MakeDomainNameFromDNSNameString(&d, resolver->search[j]) != NULL)
             {
-                char interface_buf[32];
-                mDNS_snprintf(interface_buf, sizeof(interface_buf), "for interface %s", InterfaceNameForID(&mDNSStorage,  interfaceId));
-                LogInfo("ConfigSearchDomains: (%s) configuring search domain %s %s (generation= %llu)", scopeString,
-                        resolver->search[j], (interfaceId == mDNSInterface_Any) ? "" : interface_buf, generation);
+                LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
+                    "ConfigSearchDomains -- search domain: " PRI_DM_NAME, DM_NAME_PARAM_NONNULL(&d));
+
                 UpdateSearchDomainHash(sdc, resolver->search[j], interfaceId);
                 mDNS_AddSearchDomain_CString(resolver->search[j], interfaceId);
             }
             else
             {
-                LogInfo("ConfigSearchDomains: An invalid search domain was detected for %s domain %s n_nameserver %d, (generation= %llu)",
-                        DNSScopeToString(scope), resolver->domain, resolver->n_nameserver, generation);
+                LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_ERROR,
+                    "ConfigSearchDomains: An invalid search domain was detected -- index: %d, name server count: %d",
+                    j,resolver->n_nameserver);
             }
         }
     }
     else
     {
-        LogInfo("ConfigSearchDomains: (%s) Ignoring search domain for interface %s", scopeString, InterfaceNameForID(&mDNSStorage, interfaceId));
+        LogRedactWithIfID(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT, interfaceId,
+            "ConfigSearchDomains: Ignoring search domains for interface -- scope type: " PUB_DNS_SCOPE_TYPE,
+            DNS_SCOPE_TYPE_PARAM(scope));
     }
+#undef LogRedactWithIfID
 }
 
 mDNSlocal mDNSInterfaceID ConfigParseInterfaceID(mDNSu32 ifindex)
@@ -3833,7 +3922,6 @@
     int i;
     dns_resolver_t **resolver;
     int nresolvers;
-    const char *scopeString = DNSScopeToString(scope);
     mDNSInterfaceID interface;
 
     switch (scope)
@@ -3859,7 +3947,10 @@
     {
         dns_resolver_t *r = resolver[i];
 
-        LogInfo("ConfigResolvers: %s resolver[%d] domain %s n_nameserver %d", scopeString, i, r->domain, r->n_nameserver);
+        // ConfigResolvers -- scope type: Unscoped, resolver[6]: {domain: b.e.f.ip6.arpa, name server count: 0}
+        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
+            "ConfigResolvers -- scope type: " PUB_DNS_SCOPE_TYPE ", resolver[%d]: {domain: " PRI_S
+            ", name server count: %d}", DNS_SCOPE_TYPE_PARAM(scope), i, r->domain, r->n_nameserver);
 
         interface = mDNSInterface_Any;
 
@@ -4159,12 +4250,11 @@
     if (RegDomains   ) *RegDomains     = NULL;
     if (BrowseDomains) *BrowseDomains  = NULL;
 
-    LogInfo("mDNSPlatformSetDNSConfig:%s%s%s%s%s",
-            setservers    ? " setservers"    : "",
-            setsearch     ? " setsearch"     : "",
-            fqdn          ? " fqdn"          : "",
-            RegDomains    ? " RegDomains"    : "",
-            BrowseDomains ? " BrowseDomains" : "");
+    // mDNSPlatformSetDNSConfig new updates -- setservers: yes, setsearch: no, fqdn: yes, RegDomains: no, BrowseDomains: no
+    LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
+        "mDNSPlatformSetDNSConfig new updates -- setservers: " PUB_BOOL ", setsearch: " PUB_BOOL ", fqdn: " PUB_BOOL
+        ", RegDomains: " PUB_BOOL ", BrowseDomains: " PUB_BOOL, setservers, setsearch, fqdn != mDNSNULL,
+        RegDomains != mDNSNULL, BrowseDomains != mDNSNULL);
 
     if (setsearch) MD5_Init(&sdc);
 
@@ -4221,11 +4311,18 @@
             // Accordingly, we suppress syslog messages for the first three minutes after boot.
             // If we are still getting failures after three minutes, then we log them.
             if ((mDNSu32)mDNSPlatformRawTime() > (mDNSu32)(mDNSPlatformOneSecond * 180))
-                LogMsg("mDNSPlatformSetDNSConfig: Error: dns_configuration_copy returned NULL");
+            {
+                LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_ERROR,
+                    "mDNSPlatformSetDNSConfig Error: dns_configuration_copy returned NULL");
+            }
         }
         else
         {
-            LogInfo("mDNSPlatformSetDNSConfig: config->n_resolver = %d, generation %llu, last %llu", config->n_resolver, config->generation, m->p->LastConfigGeneration);
+            //  mDNSPlatformSetDNSConfig -- config->n_resolver: 7, this config generagtion: 10267971247541, last config generation: 10267774267674, changed: yes
+            LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT, "mDNSPlatformSetDNSConfig -- "
+                "config->n_resolver: %d, this config generagtion: %llu, last config generation: %llu, changed: " PUB_BOOL,
+                config->n_resolver, config->generation, m->p->LastConfigGeneration,
+                (config->generation != m->p->LastConfigGeneration));
 
             // For every network change, mDNSPlatformSetDNSConfig is called twice. First,
             // to update the search domain list (in which case, the setsearch bool is set);
@@ -4248,7 +4345,6 @@
             if (setservers && !m->p->if_interface_changed && m->p->LastConfigGeneration == config->generation)
 #endif
             {
-                LogInfo("mDNSPlatformSetDNSConfig(setservers): generation number %llu same, not processing", config->generation);
                 dns_configuration_free(config);
                 SetupDDNSDomains(fqdn, RegDomains, BrowseDomains);
                 return mDNSfalse;
@@ -4313,7 +4409,7 @@
                 // As we ack only when we process dns servers, we set the generation number
                 // during acking.
                 m->p->LastConfigGeneration = config->generation;
-                LogInfo("mDNSPlatformSetDNSConfig: Acking configuration setservers %d, setsearch %d", setservers, setsearch);
+                LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT, "mDNSPlatformSetDNSConfig: acking configuration");
                 AckConfigd(config);
             }
             dns_configuration_free(config);
@@ -4743,11 +4839,11 @@
     if (!m->NetworkChanged || m->NetworkChanged - NonZeroTime(m->timenow + delay) > 0)
     {
         m->NetworkChanged = NonZeroTime(m->timenow + delay);
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "SetNetworkChanged: Scheduling in %d ticks", delay);
+        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT, "SetNetworkChanged: Scheduling in %d ticks", delay);
     }
     else
    	{
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
+        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
             "SetNetworkChanged: *NOT* increasing delay from %d to %d", m->NetworkChanged - m->timenow, delay);
     }
 }
@@ -4767,10 +4863,10 @@
 mDNSexport void mDNSMacOSXNetworkChanged(void)
 {
     mDNS *const m = &mDNSStorage;
-    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
-        "*** Network Configuration Change ***  %d ticks late" PUB_S,
-        m->NetworkChanged ? mDNS_TimeNow(m) - m->NetworkChanged : 0,
-        m->NetworkChanged ? "" : " (no scheduled configuration change)");
+    const mDNSs32 delay = m->NetworkChanged ? mDNS_TimeNow(m) - m->NetworkChanged : 0;
+    LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
+        "*** Network Configuration Change *** -- network changed: " PUB_BOOL ", delay: %d ticks",
+        BOOL_PARAM(m->NetworkChanged), delay);
     m->NetworkChanged = 0;       // If we received a network change event and deferred processing, we're now dealing with it
 
     // If we have *any* TENTATIVE IPv6 addresses, wait until they've finished configuring
@@ -4795,7 +4891,7 @@
                 {
                     if (ifr6.ifr_ifru.ifru_flags6 & IN6_IFF_TENTATIVE)
                     {
-                        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
+                        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
                             "*** Network Configuration Change ***  IPv6 address " PRI_IPv6_ADDR " TENTATIVE, will retry",
                             &ifr6.ifr_addr.sin6_addr);
                         tentative = mDNStrue;
@@ -4814,7 +4910,7 @@
             mDNS_Unlock(m);
             return;
         }
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
+        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
             "*** Network Configuration Change ***  No IPv6 address TENTATIVE, will continue");
     }
 
@@ -4890,14 +4986,12 @@
 
     if (!dict)
     {
-        LogMsg("ChangedKeysHaveIPv4LL: No dictionary");
         goto done;
     }
 
     ic = CFDictionaryGetCount(dict);
     if (ic <= 0)
     {
-        LogMsg("ChangedKeysHaveIPv4LL: Empty dictionary");
         goto done;
     }
 
@@ -4920,7 +5014,6 @@
         if (mDNS_LoggingEnabled)
         {
             if (!CFStringGetCString(ifname, buf, sizeof(buf), kCFStringEncodingUTF8)) buf[0] = 0;
-            LogInfo("ChangedKeysHaveIPv4LL: potential ifname %s", buf);
         }
 
         // Loop over the interfaces to find matching the ifname, and see if that one has kSCValNetIPv4ConfigMethodLinkLocal
@@ -4940,11 +5033,6 @@
             if (!CFEqual(ifname, name)) continue;
 
             if ((serviceid = CopyNameFromKey(keys[i])) == NULL) continue;
-            if (mDNS_LoggingEnabled)
-            {
-                if (!CFStringGetCString(serviceid, buf, sizeof(buf), kCFStringEncodingUTF8)) buf[0] = 0;
-                LogInfo("ChangedKeysHaveIPv4LL: found serviceid %s", buf);
-            }
 
             pattern = SCDynamicStoreKeyCreateNetworkServiceEntity(NULL, kSCDynamicStoreDomainSetup, serviceid, kSCEntNetIPv4);
             MDNS_DISPOSE_CF_OBJECT(serviceid);
@@ -4957,12 +5045,6 @@
             configmethod = CFDictionaryGetValue(ipv4dict, kSCPropNetIPv4ConfigMethod);
             if (!configmethod) continue;
 
-            if (mDNS_LoggingEnabled)
-            {
-                if (!CFStringGetCString(configmethod, buf, sizeof(buf), kCFStringEncodingUTF8)) buf[0] = 0;
-                LogInfo("ChangedKeysHaveIPv4LL: configmethod %s", buf);
-            }
-
             if (CFEqual(configmethod, kSCValNetIPv4ConfigMethodLinkLocal)) { found++; break; }
         }
 
@@ -4977,6 +5059,37 @@
     return found;
 }
 
+mDNSlocal mDNSNetworkChangeEventFlags_t GetNetworkChangeEventFlags(const mDNSBool c_host, const mDNSBool c_comp,
+    const mDNSBool c_udns, const mDNSBool c_ddns, const mDNSBool c_v4ll, const mDNSBool c_fast)
+{
+    mDNSNetworkChangeEventFlags_t opts = mDNSNetworkChangeEventFlag_None;
+    if (c_host)
+    {
+        opts |= mDNSNetworkChangeEventFlag_LocalHostname;
+    }
+    if (c_comp)
+    {
+        opts |= mDNSNetworkChangeEventFlag_ComputerName;
+    }
+    if (c_udns)
+    {
+        opts |= mDNSNetworkChangeEventFlag_DNS;
+    }
+    if (c_ddns)
+    {
+        opts |= mDNSNetworkChangeEventFlag_DynamicDNS;
+    }
+    if (c_v4ll)
+    {
+        opts |= mDNSNetworkChangeEventFlag_IPv4LL;
+    }
+    if (c_fast)
+    {
+        opts |= mDNSNetworkChangeEventFlag_P2PLike;
+    }
+    return opts;
+}
+
 mDNSlocal void NetworkChanged(SCDynamicStoreRef store, CFArrayRef changedKeys, void *context)
 {
     (void)store;        // Parameter not used
@@ -5030,7 +5143,6 @@
                 if (CFStringGetCString(CFArrayGetValueAtIndex(labels, 3), buf, sizeof(buf), kCFStringEncodingUTF8)
                     && (strstr(buf, "p2p") || (getExtendedFlags(buf) & (IFEF_DIRECTLINK | IFEF_AWDL)) || util_is_car_play(buf)))
                 {
-                    LogInfo("NetworkChanged: interface %s qualifies for reduced change handling delay", buf);
                     c_fast++;
                     MDNS_DISPOSE_CF_OBJECT(labels);
                     break;
@@ -5050,19 +5162,16 @@
         {
             char buf[256];
             if (!CFStringGetCString(CFArrayGetValueAtIndex(changedKeys, i), buf, sizeof(buf), kCFStringEncodingUTF8)) buf[0] = 0;
-            LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "*** Network Configuration Change *** SC key: " PUB_S, buf);
+            LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
+                "*** Network Configuration Change *** SC key: " PUB_S, buf);
         }
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
-            "*** Network Configuration Change *** %ld change" PUB_S " " PUB_S PUB_S PUB_S PUB_S PUB_S PUB_S "delay %d" PUB_S,
-            (long)c, c>1 ? "s" : "",
-            c_host ? "(Local Hostname) " : "",
-            c_comp ? "(Computer Name) "  : "",
-            c_udns ? "(DNS) "            : "",
-            c_ddns ? "(DynamicDNS) "     : "",
-            c_v4ll ? "(kSCValNetIPv4ConfigMethodLinkLocal) " : "",
-            c_fast ? "(P2P/IFEF_DIRECTLINK/IFEF_AWDL/IsCarPlaySSID) "  : "",
-            delay,
-            c_ddns ? " + SetKeyChainTimer" : "");
+        const mDNSNetworkChangeEventFlags_t netChangeFlags = GetNetworkChangeEventFlags(c_host, c_comp, c_udns,
+            c_ddns, c_v4ll, c_fast);
+
+        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
+            "*** Network Configuration Change *** -- "
+            "change count: %ld, delay: %d, flags: %{public, mdnsresponder:net_change_flags}d",
+            (long)c, delay, netChangeFlags);
     }
 
     SetNetworkChanged(delay);
@@ -5169,36 +5278,13 @@
     struct { struct kern_event_msg k; char extra[256]; } msg;
     ssize_t bytes = recv(s1, &msg, sizeof(msg), 0);
     if (bytes < 0)
-        LogMsg("SysEventCallBack: recv error %ld errno %d (%s)", (long)bytes, errno, strerror(errno));
+    {
+        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_ERROR, "SysEventCallBack error -- error: " PUB_OS_ERR, (long)errno);
+    }
     else
     {
-        LogInfo("SysEventCallBack got %ld bytes size %u %X %s %X %s %X %s id %d code %d %s",
-                (long)bytes, msg.k.total_size,
-                msg.k.vendor_code, msg.k.vendor_code  == KEV_VENDOR_APPLE  ? "KEV_VENDOR_APPLE"  : "?",
-                msg.k.kev_class, msg.k.kev_class    == KEV_NETWORK_CLASS ? "KEV_NETWORK_CLASS" : "?",
-                msg.k.kev_subclass, msg.k.kev_subclass == KEV_DL_SUBCLASS   ? "KEV_DL_SUBCLASS"   : "?",
-                msg.k.id, msg.k.event_code,
-                msg.k.event_code == KEV_DL_SIFFLAGS             ? "KEV_DL_SIFFLAGS"             :
-                msg.k.event_code == KEV_DL_SIFMETRICS           ? "KEV_DL_SIFMETRICS"           :
-                msg.k.event_code == KEV_DL_SIFMTU               ? "KEV_DL_SIFMTU"               :
-                msg.k.event_code == KEV_DL_SIFPHYS              ? "KEV_DL_SIFPHYS"              :
-                msg.k.event_code == KEV_DL_SIFMEDIA             ? "KEV_DL_SIFMEDIA"             :
-                msg.k.event_code == KEV_DL_SIFGENERIC           ? "KEV_DL_SIFGENERIC"           :
-                msg.k.event_code == KEV_DL_ADDMULTI             ? "KEV_DL_ADDMULTI"             :
-                msg.k.event_code == KEV_DL_DELMULTI             ? "KEV_DL_DELMULTI"             :
-                msg.k.event_code == KEV_DL_IF_ATTACHED          ? "KEV_DL_IF_ATTACHED"          :
-                msg.k.event_code == KEV_DL_IF_DETACHING         ? "KEV_DL_IF_DETACHING"         :
-                msg.k.event_code == KEV_DL_IF_DETACHED          ? "KEV_DL_IF_DETACHED"          :
-                msg.k.event_code == KEV_DL_LINK_OFF             ? "KEV_DL_LINK_OFF"             :
-                msg.k.event_code == KEV_DL_LINK_ON              ? "KEV_DL_LINK_ON"              :
-                msg.k.event_code == KEV_DL_PROTO_ATTACHED       ? "KEV_DL_PROTO_ATTACHED"       :
-                msg.k.event_code == KEV_DL_PROTO_DETACHED       ? "KEV_DL_PROTO_DETACHED"       :
-                msg.k.event_code == KEV_DL_LINK_ADDRESS_CHANGED ? "KEV_DL_LINK_ADDRESS_CHANGED" :
-                msg.k.event_code == KEV_DL_WAKEFLAGS_CHANGED    ? "KEV_DL_WAKEFLAGS_CHANGED"    :
-                msg.k.event_code == KEV_DL_IF_IDLE_ROUTE_REFCNT ? "KEV_DL_IF_IDLE_ROUTE_REFCNT" :
-                msg.k.event_code == KEV_DL_IFCAP_CHANGED        ? "KEV_DL_IFCAP_CHANGED"        :
-                msg.k.event_code == KEV_DL_LINK_QUALITY_METRIC_CHANGED    ? "KEV_DL_LINK_QUALITY_METRIC_CHANGED"    :
-                 "?");
+        LogRedact(MDNS_LOG_CATEGORY_STATE, MDNS_LOG_DEFAULT,
+            "SysEventCallBack -- event: %{public, mdnsresponder:kev_dl_event}d", msg.k.event_code);
 
         // We receive network change notifications both through configd and through SYSPROTO_EVENT socket.
         // Configd may not generate network change events for manually configured interfaces (i.e., non-DHCP)
@@ -6680,6 +6766,9 @@
     // Adding interfaces will use this flag, so set it now.
     m->DivertMulticastAdvertisements = !m->AdvertiseLocalAddresses;
 
+#if MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
+    m->SPSBrowseCallback = UpdateSPSStatus;
+#endif // MDNSRESPONDER_SUPPORTS(COMMON, SPS_CLIENT)
 
     mStatus result = mDNSPlatformInit_setup(m);
 
@@ -7022,7 +7111,7 @@
 }
 
 // Filter questions send over P2P (D2D) type interfaces.
-mDNSexport mDNSBool mDNSPlatformValidQuestionForInterface(DNSQuestion *q, const NetworkInterfaceInfo *intf)
+mDNSexport mDNSBool mDNSPlatformValidQuestionForInterface(const DNSQuestion *const q, const NetworkInterfaceInfo *const intf)
 {
     // For an explicit match to a valid interface ID, return true.
     if (q->InterfaceID == intf->InterfaceID)
@@ -7198,6 +7287,177 @@
 }
 #endif
 
+mDNSlocal void EnumerateLocalRecords(const mrcs_record_applier_t applier)
+{
+    CFMutableSetRef excludedNames = CFSetCreateMutable(kCFAllocatorDefault, 0, &mdns_domain_name_cf_set_callbacks);
+    KQueueLock();
+    if (excludedNames)
+    {
+        // Exclude .local records that have the same name as our unique AuthRecords as well as our device-info record,
+        // which is not registered as a unique record, but as an advisory record (kDNSRecordTypeAdvisory).
+        for (const AuthRecord *ar = mDNSStorage.ResourceRecords; ar; ar = ar->next)
+        {
+            const ResourceRecord *const rr = &ar->resrec;
+            if (IsSubdomain(rr->name, &localdomain))
+            {
+                if ((rr->RecordType & kDNSRecordTypeUniqueMask) || IsSubdomain(rr->name, &LocalDeviceInfoName))
+                {
+                    mdns_domain_name_t name = mdns_domain_name_create_with_labels(rr->name->c, mDNSNULL);
+                    if (name)
+                    {
+                        CFSetAddValue(excludedNames, name);
+                    }
+                    mdns_forget(&name);
+                }
+            }
+        }
+    }
+    mDNSu32 slot;
+    const CacheGroup *cg;
+    mDNS *const m = &mDNSStorage;
+    FORALL_CACHEGROUPS(slot, cg)
+    {
+        if (cg->name && IsSubdomain(cg->name, &localdomain))
+        {
+            mdns_domain_name_t name = mdns_domain_name_create_with_labels(cg->name->c, mDNSNULL);
+            if (name && (!excludedNames || !CFSetContainsValue(excludedNames, name)))
+            {
+                mDNSBool nameIsEligible = mDNSfalse;
+                const mDNSBool nameIsDeviceInfo = IsSubdomain(cg->name, &LocalDeviceInfoName);
+                const ResourceRecord *deviceInfoTXT = mDNSNULL;
+                const mDNSAddr *bestSourceAddr = mDNSNULL;
+                mDNSBool bestSourceAddrIsLinkLocal = mDNSfalse;
+                for (const CacheRecord *cr = cg->members; cr; cr = cr->next)
+                {
+                    // A cache group name is eligible for being passed to the applier if the cache group has at least
+                    // one non-NSEC cache record. The reason for this is that when our own records get deregistered,
+                    // the cached copies of the records get flushed from our local cache, but the accompanying NSEC
+                    // records currently do not. So we don't want to pass the names of records that have recently been
+                    // deregistered by us just because of stray lone NSEC records.
+                    const ResourceRecord *const rr = &cr->resrec;
+                    if (rr->rrtype == kDNSType_NSEC)
+                    {
+                        continue;
+                    }
+                    nameIsEligible = mDNStrue;
+                    const mDNSAddr *const sourceAddr = &cr->sourceAddress;
+                    if (nameIsDeviceInfo)
+                    {
+                        // If the cache group name is a subdomain of _device-info._tcp.local then the caller is
+                        // expecting to be provided with the RDATA of a device info TXT record with the same name. If
+                        // that TXT record is found, then use the source address of the TXT record instead of the source
+                        // address of a non-TXT record with the same name.
+                        if ((rr->rrtype == kDNSType_TXT) && (rr->RecordType != kDNSRecordTypePacketNegative))
+                        {
+                            deviceInfoTXT = rr;
+                            bestSourceAddr = sourceAddr;
+                            break;
+                        }
+                    }
+                    // If there are multiple source addresses from multiple records with the same name, prefer a
+                    // non-link-local address since it's more specific to the local network.
+                    if (!bestSourceAddr)
+                    {
+                        switch (sourceAddr->type)
+                        {
+                            case mDNSAddrType_IPv4:
+                            case mDNSAddrType_IPv6:
+                                bestSourceAddr = sourceAddr;
+                                bestSourceAddrIsLinkLocal = mDNSAddressIsLinkLocal(bestSourceAddr);
+                                break;
+
+                            default:
+                                break;
+                        }
+                    }
+                    else
+                    {
+                        if (bestSourceAddrIsLinkLocal && !mDNSAddressIsLinkLocal(sourceAddr))
+                        {
+                            bestSourceAddr = sourceAddr;
+                            bestSourceAddrIsLinkLocal = mDNSfalse;
+                        }
+                    }
+                    if (!nameIsDeviceInfo && bestSourceAddr && !bestSourceAddrIsLinkLocal)
+                    {
+                        break;
+                    }
+                }
+                if (nameIsEligible)
+                {
+                    sockaddr_ip sourceAddr;
+                    mDNSPlatformMemZero(&sourceAddr, sizeof(sourceAddr));
+                    sourceAddr.sa.sa_family = AF_UNSPEC;
+                    if (bestSourceAddr)
+                    {
+                        switch (bestSourceAddr->type)
+                        {
+                            case mDNSAddrType_IPv4:
+                                mdns_sockaddr_in_init(&sourceAddr.v4, mDNSVal32(bestSourceAddr->ip.v4), 0);
+                                break;
+
+                            case mDNSAddrType_IPv6:
+                                mdns_sockaddr_in6_init(&sourceAddr.v6, bestSourceAddr->ip.v6.b, 0, 0);
+                                break;
+
+                            default:
+                                break;
+                        }
+                    }
+                    const mDNSu8 *const rdata = deviceInfoTXT ? deviceInfoTXT->rdata->u.txt.c : mDNSNULL;
+                    const mDNSu16 rdlen = deviceInfoTXT ? deviceInfoTXT->rdlength : 0;
+                    applier(mdns_domain_name_get_presentation(name), rdata, rdlen, &sourceAddr);
+                }
+            }
+            mdns_forget(&name);
+        }
+    }
+    KQueueUnlock("enumerate .local records");
+    mdns_cf_forget(&excludedNames);
+}
+
+mDNSlocal void FlushRecordCache(const char *const recordNameStr, __unused const mDNSBool useKeyTag, __unused const mDNSu16 keyTag)
+{
+    domainname recordName;
+    MakeDomainNameFromDNSNameString(&recordName, recordNameStr);
+    mDNS *const m = &mDNSStorage;
+    mDNSu32 slot;
+    const CacheGroup *cg;
+    KQueueLock();
+    mDNS_Lock(m);
+    FORALL_CACHEGROUPS(slot, cg)
+    {
+        if (cg->name && SameDomainName(cg->name, &recordName))
+        {
+            for (CacheRecord *cr = cg->members; cr; cr = cr->next)
+            {
+                mDNS_PurgeCacheResourceRecord(m, cr);
+            }
+        }
+    }
+    mDNS_Unlock(m);
+    KQueueUnlock("FlushRecordCache");
+}
+
+mDNSlocal void FlushRecordCacheByName(const char *const recordNameStr)
+{
+    FlushRecordCache(recordNameStr, mDNSfalse, 0);
+}
+
+mDNSlocal void FlushRecordCacheByNameAndKeyTag(const char *const recordNameStr, const mDNSu16 keyTag)
+{
+    // The ability to flush cache records by both name and key tag will be implemented via
+    // rdar://124590981 (Add ability to flush cache records by key tag).
+    FlushRecordCache(recordNameStr, mDNStrue, keyTag);
+}
+
+const struct mrcs_server_record_cache_handlers_s kMRCServerRecordCacheHandlers =
+{
+    .enumerate_local_records = EnumerateLocalRecords,
+    .flush_by_name = FlushRecordCacheByName,
+    .flush_by_name_and_key_tag = FlushRecordCacheByNameAndKeyTag,
+};
+
 #ifdef UNIT_TEST
 #include "../unittests/mdns_macosx_ut.c"
 #endif
diff --git a/mDNSMacOSX/mDNSMacOSX.h b/mDNSMacOSX/mDNSMacOSX.h
index 54be681..89ec409 100644
--- a/mDNSMacOSX/mDNSMacOSX.h
+++ b/mDNSMacOSX/mDNSMacOSX.h
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4; c-file-style: "bsd"; c-basic-offset: 4; fill-column: 108; indent-tabs-mode: nil; -*-
  *
- * Copyright (c) 2002-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -315,4 +315,6 @@
 }
 #endif
 
+extern const struct mrcs_server_record_cache_handlers_s kMRCServerRecordCacheHandlers;
+
 #endif
diff --git a/mDNSMacOSX/secure_coding/secure-coding.xcconfig b/mDNSMacOSX/secure_coding/secure-coding.xcconfig
index 6a97c59..5ccdd20 100644
--- a/mDNSMacOSX/secure_coding/secure-coding.xcconfig
+++ b/mDNSMacOSX/secure_coding/secure-coding.xcconfig
@@ -6,4 +6,4 @@
 //
 // https://confluence.sd.apple.com/display/NETWORKING/Code+Safety+Checklist
 //
-WARNING_CFLAGS = $(inherited) -Weverything -Wno-missing-variable-declarations -Wno-reserved-id-macro -Wno-gnu-zero-variadic-macro-arguments -Wno-zero-length-array -Wno-cast-align -Wno-cstring-format-directive -Wno-nullability-extension -Wno-format-pedantic -Wno-objc-missing-property-synthesis -Wno-c++98-compat-pedantic -Wno-gnu-empty-initializer -Werror -Wno-error=deprecated-declarations -Wno-error=deprecated-implementations -Wno-error=objc-designated-initializers -Wno-error=nonnull -Wno-error=nullable-to-nonnull-conversion -Wno-error=nullability-declspec -Wno-error=#warnings -Wno-declaration-after-statement
+WARNING_CFLAGS = $(inherited) -Weverything -Wno-missing-variable-declarations -Wno-reserved-id-macro -Wno-gnu-zero-variadic-macro-arguments -Wno-zero-length-array -Wno-cast-align -Wno-cstring-format-directive -Wno-nullability-extension -Wno-format-pedantic -Wno-objc-missing-property-synthesis -Wno-c++98-compat-pedantic -Werror -Wno-error=deprecated-declarations -Wno-error=deprecated-implementations -Wno-error=objc-designated-initializers -Wno-error=nonnull -Wno-error=nullable-to-nonnull-conversion -Wno-error=nullability-declspec -Wno-error=#warnings -Wno-declaration-after-statement
diff --git a/mDNSMacOSX/secure_coding/strict.h b/mDNSMacOSX/secure_coding/strict.h
index c12381f..a2f5cc2 100644
--- a/mDNSMacOSX/secure_coding/strict.h
+++ b/mDNSMacOSX/secure_coding/strict.h
@@ -410,16 +410,16 @@
 __END_DECLS
 
 #if defined(__cplusplus)
-template<class T>
+template<class T, typename... Params>
 __attribute__((__warn_unused_result__))
 inline __attribute__((always_inline))
-T * _Nonnull strict_new()
+T * _Nonnull strict_new(Params && ...params)
 {
 	// Strictly speaking, we neither need to make sure new doesn't throw,
 	// nor check if new returned nullptr because strict_calloc is supplying
 	// the memory and it is guaranteed to either return the bytes requested
 	// or abort. But we'll check for nullptr to be extra defensive.
-	T *buffer = new (strict_calloc(1, sizeof(T))) T;
+	T *buffer = new (strict_calloc(1, sizeof(T))) T(std::forward<Params>(params)...);
 	if (_STRICT_UNLIKELY_IS_NULL(buffer)) {
 		STRICT_ABORT("strict_new(%s) failed", __PRETTY_FUNCTION__);
 		// Not reached
@@ -447,8 +447,8 @@
 	return buffer;
 }
 
-#define STRICT_NEW_TYPE(TYPE)									\
-	strict_new<TYPE>()
+#define STRICT_NEW_TYPE(TYPE, ...)								\
+	strict_new<TYPE>(__VA_ARGS__)
 
 #define STRICT_PLACEMENT_NEW_TYPE(TYPE, MEMORY)					\
 	strict_placement_new<TYPE>(MEMORY)
diff --git a/mDNSMacOSX/srp-test-server-Info.plist b/mDNSMacOSX/srp-test-server-Info.plist
new file mode 100644
index 0000000..3f50b55
--- /dev/null
+++ b/mDNSMacOSX/srp-test-server-Info.plist
@@ -0,0 +1,10 @@
+<?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>CFBundleIdentifier</key>
+        <string>com.apple.srp-test-server</string>
+        <key>CFBundleName</key>
+        <string>DNS-SD Service Registration Protocol Test Server for Multicast DNS</string>
+</dict>
+</plist>
diff --git a/mDNSMacOSX/uDNSPathEvaluation.c b/mDNSMacOSX/uDNSPathEvaluation.c
index cd19408..7bb2801 100644
--- a/mDNSMacOSX/uDNSPathEvaluation.c
+++ b/mDNSMacOSX/uDNSPathEvaluation.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2013-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -215,7 +215,9 @@
         isBlocked = mDNStrue;
     }
 #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
-    if (!isBlocked)
+    // Only set the ResolverUUID if the DNSQuestion isn't blocked and the DNS service selection algorithm isn't being
+    // overriden with the DNS service specified by ResolverUUID.
+    if (!isBlocked && !q->OverrideDNSService)
     {
         uuid_clear(q->ResolverUUID);
         if (path != NULL)
diff --git a/mDNSPosix/Makefile b/mDNSPosix/Makefile
index 0417065..134af2a 100755
--- a/mDNSPosix/Makefile
+++ b/mDNSPosix/Makefile
@@ -158,7 +158,7 @@
 ifeq ($(os),x)
 # We have to define __MAC_OS_X_VERSION_MIN_REQUIRED=__MAC_OS_X_VERSION_10_4 or on Leopard
 # we get build failures: ‘daemon’ is deprecated (declared at /usr/include/stdlib.h:283)
-CFLAGS_OS = -DHAVE_IPV6 -no-cpp-precomp -Werror -Wno-declaration-after-statement \
+CFLAGS_OS = -DHAVE_IPV6 -no-cpp-precomp -Werror -Wno-declaration-after-statement -Wno-unused-but-set-variable \
 	-D__MAC_OS_X_VERSION_MIN_REQUIRED=__MAC_OS_X_VERSION_10_4 \
 	-DHAVE_STRLCPY=1 -DTARGET_OS_MAC \
 	-D__APPLE_USE_RFC_2292 #-Wunreachable-code
diff --git a/mDNSPosix/PosixDaemon.c b/mDNSPosix/PosixDaemon.c
index 9a0f692..a917345 100644
--- a/mDNSPosix/PosixDaemon.c
+++ b/mDNSPosix/PosixDaemon.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4; c-file-style: "bsd"; c-basic-offset: 4; fill-column: 108; indent-tabs-mode: nil; -*-
  *
- * Copyright (c) 2003-2020 Apple Inc. All rights reserved.
+ * Copyright (c) 2003-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -144,6 +144,7 @@
     mDNSPosixListenForSignalInEventLoop(SIGINT);
     mDNSPosixListenForSignalInEventLoop(SIGTERM);
     mDNSPosixListenForSignalInEventLoop(SIGUSR1);
+    mDNSPosixListenForSignalInEventLoop(SIGUSR2);
     mDNSPosixListenForSignalInEventLoop(SIGPIPE);
     mDNSPosixListenForSignalInEventLoop(SIGHUP) ;
 
@@ -171,6 +172,11 @@
 
         if (sigismember(&signals, SIGHUP )) Reconfigure(m);
         if (sigismember(&signals, SIGUSR1)) DumpStateLog();
+        if (sigismember(&signals, SIGUSR2))
+        {
+            mDNS_DebugLoggingEnabled = !mDNS_DebugLoggingEnabled;
+            LogMsg("Received SIGUSR2 - %s debug level logging.", mDNS_DebugLoggingEnabled ? "Enable" : "Disable");
+        }
         // SIGPIPE happens when we try to write to a dead client; death should be detected soon in request_callback() and cleaned up.
         if (sigismember(&signals, SIGPIPE)) LogMsg("Received SIGPIPE - ignoring");
         if (sigismember(&signals, SIGINT) || sigismember(&signals, SIGTERM)) break;
@@ -184,6 +190,9 @@
 
     ParseCmdLineArgs(argc, argv);
 
+    // Enable mDNSResponder logging by default.
+    mDNS_LoggingEnabled = mDNStrue;
+
     LogMsg("%s starting", mDNSResponderVersionString);
 
     err = mDNS_Init(&mDNSStorage, &PlatformStorage, gRRCache, RR_CACHE_SIZE, mDNS_Init_AdvertiseLocalAddresses,
diff --git a/mDNSPosix/mDNSPosix.c b/mDNSPosix/mDNSPosix.c
index d574283..9d18809 100644
--- a/mDNSPosix/mDNSPosix.c
+++ b/mDNSPosix/mDNSPosix.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4; c-file-style: "bsd"; c-basic-offset: 4; fill-column: 108; indent-tabs-mode: nil; -*-
  *
- * Copyright (c) 2002-2022 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -385,8 +385,34 @@
     }
 }
 
+// Searches the interface list looking for the named interface.
+// Returns a pointer to if it found, or NULL otherwise.
+mDNSlocal PosixNetworkInterface *SearchForInterfaceByName(mDNS *const m, const char *const intfName)
+{
+    PosixNetworkInterface *intf;
+
+    assert(m != NULL);
+    assert(intfName != NULL);
+
+    intf = (PosixNetworkInterface*)(m->HostInterfaces);
+    while ((intf != NULL) && (strcmp(intf->intfName, intfName) != 0))
+        intf = (PosixNetworkInterface *)(intf->coreIntf.next);
+
+    return intf;
+}
+
+mDNSlocal PosixNetworkInterface *SearchForInterfaceByIndex(mDNS *const m, const mDNSu32 index)
+{
+    PosixNetworkInterface *intf = (PosixNetworkInterface*)(m->HostInterfaces);
+    while (intf && (((mDNSu32)intf->index) != index))
+    {
+        intf = (PosixNetworkInterface *)(intf->coreIntf.next);
+    }
+    return intf;
+}
+
 // This routine is called when the main loop detects that data is available on a socket.
-mDNSlocal void SocketDataReady(mDNS *const m, PosixNetworkInterface *intf, int skt, UDPSocket *sock)
+mDNSlocal void SocketDataReady(mDNS *const m, const PosixNetworkInterface *intf, const int skt, UDPSocket *const sock)
 {
     mDNSAddr senderAddr, destAddr;
     mDNSIPPort senderPort, destPort;
@@ -398,7 +424,6 @@
     int flags;
     mDNSu8 ttl;
     mDNSBool reject;
-    const mDNSInterfaceID InterfaceID = intf ? intf->coreIntf.InterfaceID : NULL;
 
     assert(m    != NULL);
     assert(skt  >= 0);
@@ -455,6 +480,29 @@
             if      (packetInfo.ipi_ifname[0] != 0) reject = (strcmp(packetInfo.ipi_ifname, intf->intfName) != 0);
             else if (packetInfo.ipi_ifindex != -1) reject = (packetInfo.ipi_ifindex != intf->index);
 
+            // In case a unicast packet was received on an unexpected socket, i.e., a socket associated with an
+            // interface that doesn't match the interface on which the unicast packet was actually received, then
+            // instead of immediately rejecting it, pass the message to mDNSCoreReceive() with the actual interface ID
+            // instead of the ID of the interface with which the socket is associated.
+            if (reject && !mDNSAddrIsDNSMulticast(&destAddr))
+            {
+                const PosixNetworkInterface *realIntf = mDNSNULL;
+                if (packetInfo.ipi_ifname[0] != '\0')
+                {
+                    realIntf = SearchForInterfaceByName(m, packetInfo.ipi_ifname);
+                }
+                else if (packetInfo.ipi_ifindex != -1)
+                {
+                    realIntf = SearchForInterfaceByIndex(m, (mDNSu32)packetInfo.ipi_ifindex);
+                }
+                if (realIntf)
+                {
+                    debugf("SocketDataReady correcting receive interface from %s/%u to %s/%u",
+                        intf->intfName, intf->index, realIntf->intfName, realIntf->index);
+                    intf = realIntf;
+                    reject = mDNSfalse;
+                }
+            }
             if (reject)
             {
                 verbosedebugf("SocketDataReady ignored a packet from %#a to %#a on interface %s/%d expecting %#a/%s/%d/%d",
@@ -481,8 +529,11 @@
     }
 
     if (packetLen >= 0)
+    {
+        const mDNSInterfaceID InterfaceID = intf ? intf->coreIntf.InterfaceID : NULL;
         mDNSCoreReceive(m, &packet, (mDNSu8 *)&packet + packetLen,
                         &senderAddr, senderPort, &destAddr, sock == mDNSNULL ? MulticastDNSPort : sock->port, InterfaceID);
+    }
 }
 
 mDNSlocal void UDPReadCallback(int fd, void *context)
@@ -959,22 +1010,6 @@
     return (numOfServers > 0) ? 0 : -1;
 }
 
-// Searches the interface list looking for the named interface.
-// Returns a pointer to if it found, or NULL otherwise.
-mDNSlocal PosixNetworkInterface *SearchForInterfaceByName(mDNS *const m, const char *intfName)
-{
-    PosixNetworkInterface *intf;
-
-    assert(m != NULL);
-    assert(intfName != NULL);
-
-    intf = (PosixNetworkInterface*)(m->HostInterfaces);
-    while ((intf != NULL) && (strcmp(intf->intfName, intfName) != 0))
-        intf = (PosixNetworkInterface *)(intf->coreIntf.next);
-
-    return intf;
-}
-
 mDNSexport mDNSInterfaceID mDNSPlatformInterfaceIDfromInterfaceIndex(mDNS *const m, mDNSu32 index)
 {
     PosixNetworkInterface *intf;
@@ -985,10 +1020,7 @@
     if (index == kDNSServiceInterfaceIndexP2P      ) return(mDNSInterface_P2P);
     if (index == kDNSServiceInterfaceIndexAny      ) return(mDNSInterface_Any);
 
-    intf = (PosixNetworkInterface*)(m->HostInterfaces);
-    while ((intf != NULL) && (mDNSu32) intf->index != index)
-        intf = (PosixNetworkInterface *)(intf->coreIntf.next);
-
+    intf = (PosixNetworkInterface*)SearchForInterfaceByIndex(m, index);
     return (mDNSInterfaceID) intf;
 }
 
@@ -2087,7 +2119,7 @@
     return 1;
 }
 
-mDNSexport mDNSBool mDNSPlatformValidQuestionForInterface(DNSQuestion *q, const NetworkInterfaceInfo *intf)
+mDNSexport mDNSBool mDNSPlatformValidQuestionForInterface(const DNSQuestion *const q, const NetworkInterfaceInfo *const intf)
 {
     (void) q;
     (void) intf;
diff --git a/mDNSShared/ClientRequests.c b/mDNSShared/ClientRequests.c
index a31f2bd..ce502ec 100644
--- a/mDNSShared/ClientRequests.c
+++ b/mDNSShared/ClientRequests.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2018-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -91,6 +91,7 @@
     mDNSBool                useFailover;
     mDNSBool                failoverMode;
     mDNSBool                prohibitEncryptedDNS;
+    mDNSBool                overrideDNSService;
 #endif
 #if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
     mdns_audit_token_t      peerToken;
@@ -338,6 +339,17 @@
     mDNSBool            appendSearchDomains;
     QueryRecordOpParams opParams;
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    if (inParams->overrideDNSService)
+    {
+        const mdns_audit_token_t token = inParams->peerToken;
+        const mDNSBool entitled = token && mdns_audit_token_is_entitled(token, "com.apple.private.dnssd.resolver-override");
+        mdns_require_action_quiet(entitled, exit, err = mStatus_NoAuth);
+        mdns_require_action_quiet(inParams->resolverUUID, exit, err = mStatus_BadParamErr);
+
+        Querier_RegisterPathResolver(inParams->resolverUUID);
+    }
+#endif
     err = InterfaceIndexToInterfaceID(inParams->interfaceIndex, &interfaceID);
     if (err) goto exit;
 
@@ -376,6 +388,7 @@
     opParams.useFailover            = inParams->useFailover;
     opParams.failoverMode           = inParams->failoverMode;
     opParams.prohibitEncryptedDNS   = inParams->prohibitEncryptedDNS;
+    opParams.overrideDNSService     = inParams->overrideDNSService;
 #endif
 #if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
     opParams.peerToken              = inParams->peerToken;
@@ -469,9 +482,27 @@
             #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
                 mDNSPlatformMemCopy(inQuestion->ResolverUUID, op->resolverUUID, UUID_SIZE);
             #endif
-                QueryRecordOpStartQuestion(op, inQuestion);
+                const domainname *domain = mDNSNULL;
+                // If we're appending search domains, the DNSQuestion needs to be retried without Optimistic DNS,
+                // but with the search domain we just used, so restore the search list index to avoid skipping to
+                // the next search domain.
+                //
+                // Note that when AppendSearchDomains is true, searchListIndex is the index of the next search
+                // domain to try. So if searchListIndex is 0 or negative, that means that we are not currently in
+                // the middle of iterating the search domain list, so no search domain needs to be restored. If
+                // searchListIndex is greater than 0, then we're currently in the middle of iterating through the
+                // search domain list, so the search domain that's currently in effect needs to be restored.
+                if (inQuestion->AppendSearchDomains && (op->searchListIndex > 0))
+                {
+                    op->searchListIndex = op->searchListIndexLast;
+                    domain = NextSearchDomain(op);
+                }
+                QueryRecordOpRestartUnicastQuestion(op, inQuestion, domain);
             }
             break;
+
+        MDNS_COVERED_SWITCH_DEFAULT:
+            break;
     }
 }
 
@@ -506,8 +537,9 @@
     inOp->useFailover          = inParams->useFailover;
     inOp->failoverMode         = inParams->failoverMode;
     inOp->prohibitEncryptedDNS = inParams->prohibitEncryptedDNS;
+    inOp->overrideDNSService   = inParams->overrideDNSService;
     inOp->qtype                = inParams->qtype;
-    if (!inOp->prohibitEncryptedDNS && inParams->resolverUUID)
+    if ((!inOp->prohibitEncryptedDNS || inOp->overrideDNSService) && inParams->resolverUUID)
     {
         mDNSPlatformMemCopy(inOp->resolverUUID, inParams->resolverUUID, UUID_SIZE);
     }
@@ -551,6 +583,7 @@
     q->RequireEncryption          = inParams->needEncryption;
     q->CustomID                   = inParams->customID;
     q->ProhibitEncryptedDNS       = inOp->prohibitEncryptedDNS;
+    q->OverrideDNSService         = inOp->overrideDNSService;
     if (inOp->failoverMode)
     {
         q->IsFailover = mDNStrue;
@@ -583,6 +616,20 @@
     // them on the wire as a single label query. - Mohan
 
     if (q->AppendSearchDomains && DomainNameIsSingleLabel(inOp->qname)) q->InterfaceID = mDNSInterface_LocalOnly;
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, UNICAST_DOTLOCAL)
+    // Copy the original question to another question that can be used for the legacy unicast dotlocal query, before the
+    // the original question is started and initialized.
+    const mDNSBool startParallelLegacyUnicastDotLocal = ((RecordTypeIsAddress(q->qtype) || VALID_MSAD_SRV(&inOp->q)) &&
+        !q->ForceMCast && SameDomainLabel(LastLabel(&q->qname), (const mDNSu8 *)&localdomain));
+    if (startParallelLegacyUnicastDotLocal)
+    {
+        inOp->q2 = mDNSPlatformMemAllocateClear(sizeof(*inOp->q2));
+        mdns_require_action_quiet(inOp->q2, exit, err = mStatus_NoMemoryErr);
+        *inOp->q2 = *q;
+    }
+#endif
+
     err = QueryRecordOpStartQuestion(inOp, q);
     if (err) goto exit;
 
@@ -594,21 +641,10 @@
 #endif
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, UNICAST_DOTLOCAL)
-    if ((RecordTypeIsAddress(q->qtype) || VALID_MSAD_SRV(&inOp->q)) && !q->ForceMCast &&
-        SameDomainLabel(LastLabel(&q->qname), (const mDNSu8 *)&localdomain))
+    if (startParallelLegacyUnicastDotLocal)
     {
-        DNSQuestion *       q2;
-
-        q2 = (DNSQuestion *) mDNSPlatformMemAllocate((mDNSu32)sizeof(*inOp->q2));
-        if (!q2)
-        {
-            err = mStatus_NoMemoryErr;
-            goto exit;
-        }
-        inOp->q2 = q2;
-
-        *q2 = *q;
-        q2->IsUnicastDotLocal = mDNStrue;
+        DNSQuestion *const q2 = inOp->q2;
+        mdns_require_action_quiet(q2, exit, err = mStatus_BadStateErr);
 
         if ((CountLabels(&q2->qname) == 2) && !SameDomainName(&q2->qname, &ActiveDirectoryPrimaryDomain)
             && !DomainNameIsInSearchList(&q2->qname, mDNSfalse))
@@ -621,11 +657,13 @@
 
             AssignDomainName(&q2->qname, &localdomain);
             q2->qtype                   = kDNSType_SOA;
-            q2->LongLived               = mDNSfalse;
             q2->ReturnIntermed          = mDNStrue;
             q2->TimeoutQuestion         = mDNSfalse;
             q2->AppendSearchDomains     = mDNSfalse;
         }
+        q2->IsUnicastDotLocal = mDNStrue;
+        // We disable DNS push for this legacy unicast dotlocal query.
+        q2->LongLived = mDNSfalse;
 
         LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
                "[R%u] QueryRecordOpStart: starting parallel unicast query for " PRI_DM_NAME " " PUB_S,
@@ -798,6 +836,7 @@
                 if (inQuestion->AppendSearchDomains)
                 {
                     op->searchListIndex = 0; // Reset search list usage
+                    op->searchListIndexLast = 0;
                     domain = NextSearchDomain(op);
                 }
                 QueryRecordOpRestartUnicastQuestion(op, inQuestion, domain);
@@ -894,6 +933,7 @@
         inQuestion->InterfaceID = op->interfaceID;
     }
     op->searchListIndex = 0;
+    op->searchListIndexLast = 0;
 }
 
 mDNSlocal mStatus QueryRecordOpStartQuestion(QueryRecordOp *inOp, DNSQuestion *inQuestion)
@@ -1034,6 +1074,7 @@
 {
     const domainname *      domain;
 
+    inOp->searchListIndexLast = inOp->searchListIndex;
     while ((domain = uDNS_GetNextSearchDomain(inOp->interfaceID, &inOp->searchListIndex, mDNSfalse)) != mDNSNULL)
     {
         if ((DomainNameLength(inOp->qname) - 1 + DomainNameLength(domain)) <= MAX_DOMAIN_NAME) break;
@@ -1143,3 +1184,17 @@
     }
 }
 #endif
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
+mDNSexport mDNSBool ClientRequestUsesAWDL(const uint32_t ifindex, const DNSServiceFlags flags)
+{
+    if (ifindex == kDNSServiceInterfaceIndexAny)
+    {
+        return ((flags & kDNSServiceFlagsIncludeAWDL) != 0);
+    }
+    else
+    {
+        return mDNSPlatformInterfaceIsAWDL((mDNSInterfaceID)((uintptr_t)ifindex));
+    }
+}
+#endif
diff --git a/mDNSShared/ClientRequests.h b/mDNSShared/ClientRequests.h
index facd9f8..15f11b6 100644
--- a/mDNSShared/ClientRequests.h
+++ b/mDNSShared/ClientRequests.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2018-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2018-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -36,6 +36,7 @@
     void *                      resultContext;          // Context to pass to result handler.
     mDNSu32                     reqID;                  // Client request ID.
     int                         searchListIndex;        // Index that indicates the next search domain to try.
+    int                         searchListIndexLast;    // Value of searchListIndex prior to calling NextSearchDomain().
 #if MDNSRESPONDER_SUPPORTS(APPLE, UNICAST_DOTLOCAL)
     DNSQuestion *               q2;                     // DNSQuestion for unicast version of a record with a dot-local name.
     mDNSu16                     q2Type;                 // q2's original qtype value.
@@ -54,6 +55,7 @@
     mDNSBool                    useFailover;            // Use DNS service failover if applicable.
     mDNSBool                    failoverMode;           // Use DNS service failover immediately.
     mDNSBool                    prohibitEncryptedDNS;   // Prohibit use of encrypted DNS protocols.
+    mDNSBool                    overrideDNSService;     // True if resolver UUID overrides DNS service selection.
     mDNSu8                      resolverUUID[UUID_SIZE];// Resolver UUID to use with original QNAME.
 #endif
 #if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
@@ -78,7 +80,7 @@
     QueryRecordOp       op; // Query record operation object.
 
 }   QueryRecordClientRequest;
-mdns_compile_time_max_size_check(QueryRecordClientRequest, 792);
+mdns_compile_time_max_size_check(QueryRecordClientRequest, 816);
 
 typedef struct
 {
@@ -128,6 +130,7 @@
     mDNSBool                useFailover;
     mDNSBool                failoverMode;
     mDNSBool                prohibitEncryptedDNS;
+    mDNSBool                overrideDNSService;
 #endif
 #if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
     mdns_audit_token_t      peerToken;
@@ -159,6 +162,9 @@
 mDNSexport const domainname * QueryRecordClientRequestGetQName(const QueryRecordClientRequest *inRequest);
 mDNSexport mDNSu16 QueryRecordClientRequestGetType(const QueryRecordClientRequest *inRequest);
 mDNSexport mDNSBool QueryRecordClientRequestIsMulticast(QueryRecordClientRequest *inRequest);
+#if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
+mDNSexport mDNSBool ClientRequestUsesAWDL(uint32_t ifindex, DNSServiceFlags flags);
+#endif
 
 #ifdef __cplusplus
 }
diff --git a/mDNSShared/PlatformCommon.c b/mDNSShared/PlatformCommon.c
index 9ce1546..5979377 100644
--- a/mDNSShared/PlatformCommon.c
+++ b/mDNSShared/PlatformCommon.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4; c-file-style: "bsd"; c-basic-offset: 4; fill-column: 108; indent-tabs-mode: nil; -*-
  *
- * Copyright (c) 2004-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2004-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -32,6 +32,7 @@
 #include <time.h>
 #include <sys/time.h>           // Needed for #include <sys/time.h>().
 #include <assert.h>
+#include <limits.h>
 
 
 #include "mDNSEmbeddedAPI.h"    // Defines the interface provided to the client layer above
@@ -300,8 +301,12 @@
             case MDNS_LOG_ERROR:     syslog_level = LOG_ERR;     break;
             case MDNS_LOG_WARNING:   syslog_level = LOG_WARNING; break;
             case MDNS_LOG_DEFAULT:   syslog_level = LOG_NOTICE;  break;
-            case MDNS_LOG_INFO:      syslog_level = LOG_INFO;    break;
-            case MDNS_LOG_DEBUG:     syslog_level = LOG_DEBUG;   break;
+            case MDNS_LOG_INFO:      syslog_level = LOG_NOTICE;  break;
+            case MDNS_LOG_DEBUG:
+            {
+                syslog_level = (mDNS_DebugLoggingEnabled ? LOG_NOTICE : LOG_DEBUG);
+                break;
+            }
             default:                 syslog_level = LOG_NOTICE;  break;
         }
 
diff --git a/mDNSShared/discover_resolver.c b/mDNSShared/discover_resolver.c
index 1b5f93a..4a94822 100644
--- a/mDNSShared/discover_resolver.c
+++ b/mDNSShared/discover_resolver.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2020-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2020-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -668,10 +668,9 @@
 bool
 dns_question_requires_resolver_discovery(const DNSQuestion * NONNULL q, const domainname ** const out_domain)
 {
-	// Currently, we only support discovering local resolvers for "openthread.thread.home.arpa."
-	// We only do the discovery when the question is not forced to use multicast DNS, because mDNS does not use resolver.
-	if (!q->ForceMCast && IsSubdomain(&q->qname, THREAD_DOMAIN_NAME)) {
-		*out_domain = THREAD_DOMAIN_NAME;
+	if (!q->ForceMCast && !IsRootDomain(Do53_UNICAST_DISCOVERY_DOMAIN)
+		&& IsSubdomain(&q->qname, Do53_UNICAST_DISCOVERY_DOMAIN)) {
+		*out_domain = Do53_UNICAST_DISCOVERY_DOMAIN;
 		return true;
 	} else {
 		*out_domain = NULL;
@@ -807,12 +806,20 @@
 
 //======================================================================================================================
 
-static bool
-_native_dns_service_register(const domainname * const domain, discover_resolver_name_t * const resolver_name)
+MDNS_CLOSED_ENUM(_resolver_dns_service_update_result_t, int8_t,
+	_resolver_dns_service_update_result_error				= -1,
+	_resolver_dns_service_update_result_no_change			= 0,
+	_resolver_dns_service_update_result_newly_registered	= 1,
+	_resolver_dns_service_update_result_updated				= 2,
+	_resolver_dns_service_update_result_deregistered		= 3
+);
+
+static _resolver_dns_service_update_result_t
+_native_dns_service_update(const domainname * const domain, discover_resolver_name_t * const resolver_name)
 {
 	(void)domain;
 	(void)resolver_name;
-	return false;
+	return _resolver_dns_service_update_result_no_change;
 }
 
 //======================================================================================================================
@@ -837,7 +844,7 @@
 
 	mDNS_Lock(m);
 	char if_name[64]; // The same size as the ((NetworkInterfaceInfo *)0)->ifname).
-	check_compile_time_code(sizeof(if_name) == sizeof(((NetworkInterfaceInfo *)0)->ifname));
+	mdns_compile_time_check_local(sizeof(if_name) == sizeof(((NetworkInterfaceInfo *)0)->ifname));
 
 	const char * const if_name_ptr = InterfaceNameForID(m, answer->InterfaceID);
 	if (if_name_ptr) {
@@ -847,7 +854,7 @@
 	}
 	mDNS_Unlock(m);
 
-	const char * action = NULL;
+	bool address_add;
 	mDNSAddr addr_changed;
 
 	mdns_require_quiet(change_event == QC_add ||change_event == QC_rmv, exit);
@@ -865,17 +872,12 @@
 		// Should have no duplicate addresses for the QC_add event.
 		mdns_require_quiet(resolver_addr == NULL, exit);
 
-		if (resolver_name->addresses == NULL) {
-			action = "newly added";
-		} else {
-			action = "added into the existing one";
-		}
-
 		// Add the address into list.
 		resolver_addr = resolver_addresses_add(addr_data, addr_type, &resolver_name->addresses);
 		mdns_require_quiet(resolver_addr != NULL, exit);
 		memcpy(&addr_changed, &resolver_addr->addr, sizeof(addr_changed));
 
+		address_add = true;
 	} else {
 		// Should be the added address in the list and should not be removed twice.
 		mdns_require_quiet(resolver_addr != NULL, exit);
@@ -885,14 +887,16 @@
 
 		// Remove the address from the list.
 		resolver_addresses_remove(addr_data, addr_type, &resolver_name->addresses);
+
+		address_add = false;
 	}
 
 	// Schedule new DNS configuration update.
 	_schedule_dns_service_update(resolver_name);
 
-	LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "[Q%u] Resolver " PUB_S " - "
+	LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "[Q%u] Resolver " PUB_ADD_RMV " - "
 		"browsing domain: " PRI_DM_NAME ", resolver name: " PRI_DM_NAME ", address: " PRI_IP_ADDR
-		", interface: " PUB_S ".", mDNSVal16(q->TargetQID), action,
+		", interface: " PUB_S ".", mDNSVal16(q->TargetQID), ADD_RMV_PARAM(address_add),
 		DM_NAME_PARAM(&context->ns_question.qname), DM_NAME_PARAM(&q->qname), &addr_changed, if_name);
 
 exit:
@@ -1148,15 +1152,11 @@
 			continue;
 		}
 
-		LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "Updating discovered local resolver - name: "
-			PRI_DM_NAME, DM_NAME_PARAM(&discover_resolver->domain));
+		const _resolver_dns_service_update_result_t err = _native_dns_service_update(&discover_resolver->domain,
+			resolver_name);
 
-		const mDNSBool updated = _native_dns_service_register(&discover_resolver->domain, resolver_name);
-		if (!updated) {
-			LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_FAULT,
-				"Failed to update the DNS service for the locally-discovered resolver - domain: " PRI_DM_NAME,
-				DM_NAME_PARAM(&resolver_name->resolver_name));
-		}
+		LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT, "Discovered local resolver configuration updated"
+			" - name: " PRI_DM_NAME ", result: %d", DM_NAME_PARAM(&discover_resolver->domain), err);
 
 		resolver_name->next_update_time = 0;
 	}
diff --git a/mDNSShared/dns_objects/base/ref_count.h b/mDNSShared/dns_objects/base/ref_count.h
index debefa8..9099633 100644
--- a/mDNSShared/dns_objects/base/ref_count.h
+++ b/mDNSShared/dns_objects/base/ref_count.h
@@ -25,6 +25,7 @@
 #include <stdint.h>
 #include <stddef.h>	// For offsetof().
 
+#include "general.h"
 #include "nullability.h"
 #include "dns_assert_macros.h"
 
@@ -126,13 +127,15 @@
 //======================================================================================================================
 
 // Define the kind instance of the reference count object.
-#define REF_COUNT_OBJECT_DEFINE_KIND_INSTANCE_BASIC(FAMILY_NAME, NAME, ...)		\
-	const struct ref_count_kind_s _ ## FAMILY_NAME ## _ ## NAME ## _kind = { 	\
-		.superkind	= &ref_count_kind,											\
-		.name		= # FAMILY_NAME "_" # NAME,									\
-		.finalize	= _ ## FAMILY_NAME ## _ ## NAME ## _finalize,				\
-		__VA_ARGS__																\
-	};																			\
+#define REF_COUNT_OBJECT_DEFINE_KIND_INSTANCE_BASIC(FAMILY_NAME, NAME, ...)				\
+	const struct ref_count_kind_s _ ## FAMILY_NAME ## _ ## NAME ## _kind = { 			\
+		MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_BEGIN()	\
+		.superkind	= &ref_count_kind,													\
+		.name		= # FAMILY_NAME "_" # NAME,											\
+		.finalize	= _ ## FAMILY_NAME ## _ ## NAME ## _finalize,						\
+		__VA_ARGS__																		\
+		MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_END()		\
+	};																					\
 	OBJECT_BASE_CHECK(FAMILY_NAME ## _ ## NAME, ref_count_obj)
 
 #define REF_COUNT_OBJECT_DEFINE_KIND_INSTANCE(FAMILY_NAME, NAME)				\
@@ -198,12 +201,14 @@
 
 #define REF_COUNT_OBJECT_SUBKIND_DEFINE_KIND_INSTANCE_ABSTRUCT(FAMILY_NAME, SUPER, NAME, ...)					\
 	const struct FAMILY_NAME ## _ ## SUPER ## _kind_s _ ## FAMILY_NAME ## _ ## SUPER ## _ ## NAME ## _kind = {	\
+		MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_BEGIN()							\
 		.base = {																								\
 			.superkind	= &_ ## FAMILY_NAME ## _ ## SUPER ## _kind,												\
 			.name 		= # FAMILY_NAME "_" # SUPER "_" # NAME,													\
 		},																										\
 		.FAMILY_NAME ## _ ## SUPER ## _init_fields = FAMILY_NAME ## _ ## SUPER ## _init_fields,					\
 		__VA_ARGS__																								\
+		MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_END()								\
 	};																											\
 	OBJECT_BASE_CHECK(FAMILY_NAME ## _ ## SUPER ## _ ## NAME, FAMILY_NAME ## _ ## SUPER)
 
@@ -221,6 +226,7 @@
 
 #define REF_COUNT_OBJECT_SUBKIND_DEFINE_KIND_INSTANCE_WITHOUT_COMPARATOR(FAMILY_NAME, SUPER, NAME, ...)			\
 	const struct FAMILY_NAME ## _ ## SUPER ## _kind_s _ ## FAMILY_NAME ## _ ## SUPER ## _ ## NAME ## _kind = {	\
+		MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_BEGIN()							\
 		.base = {																								\
 			.superkind	= &_ ## FAMILY_NAME ## _ ## SUPER ## _kind,												\
 			.name 		= # FAMILY_NAME "_" # SUPER "_" # NAME,													\
@@ -228,16 +234,19 @@
 		},																										\
 		.FAMILY_NAME ## _ ## SUPER ## _init_fields = FAMILY_NAME ## _ ## SUPER ## _init_fields,					\
 		__VA_ARGS__																								\
+		MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_END()								\
 	};																											\
 	OBJECT_BASE_CHECK(FAMILY_NAME ## _ ## SUPER ## _ ## NAME, FAMILY_NAME ## _ ## SUPER)
 
 #define REF_COUNT_OBJECT_SUBKIND_DEFINE_KIND_INSTANCE_FULL(FAMILY_NAME, SUPER, NAME, ...)						\
 	const struct FAMILY_NAME ## _ ## SUPER ## _kind_s _ ## FAMILY_NAME ## _ ## SUPER ## _ ## NAME ## _kind = {	\
 		.base = {																								\
+			MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_BEGIN()						\
 			.superkind	= &_ ## FAMILY_NAME ## _ ## SUPER ## _kind,												\
 			.name 		= # FAMILY_NAME "_" # SUPER "_" # NAME,													\
 			.compare	= _ ## FAMILY_NAME ## _ ## SUPER ## _ ## NAME ## _compare,								\
 			.finalize	= _ ## FAMILY_NAME ## _ ## SUPER ## _ ## NAME ## _finalize								\
+			MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_END()							\
 		},																										\
 		.FAMILY_NAME ## _ ## SUPER ## _init_fields = FAMILY_NAME ## _ ## SUPER ## _init_fields,					\
 		__VA_ARGS__																								\
diff --git a/mDNSShared/dns_objects/objs/dns_obj_rr_nsec3.c b/mDNSShared/dns_objects/objs/dns_obj_rr_nsec3.c
index b83bd65..15d146c 100644
--- a/mDNSShared/dns_objects/objs/dns_obj_rr_nsec3.c
+++ b/mDNSShared/dns_objects/objs/dns_obj_rr_nsec3.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Apple Inc. All rights reserved.
+ * Copyright (c) 2022-2023 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -240,7 +240,7 @@
 {
 	// Validating resolvers SHOULD return an insecure response when processing NSEC3 records with iterations larger
 	// than 100.
-	// See https://datatracker.ietf.org/doc/draft-ietf-dnsop-nsec3-guidance/
+	// See https://datatracker.ietf.org/doc/html/rfc9276
 	const uint16_t max_reasonable_iterations = 100;
 	return dns_obj_rr_nsec3_get_iterations(me) <= max_reasonable_iterations;
 }
diff --git a/mDNSShared/dns_objects/utilities/base_encoding.c b/mDNSShared/dns_objects/utilities/base_encoding.c
index ed8caec..4653a33 100644
--- a/mDNSShared/dns_objects/utilities/base_encoding.c
+++ b/mDNSShared/dns_objects/utilities/base_encoding.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021-2022 Apple Inc. All rights reserved.
+ * Copyright (c) 2021-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -90,6 +90,8 @@
 		case base_encoding_type_base32_hex_without_padding:
 			base_32_hex_encode(data, data_len, true, encoded_str);
 			break;
+		MDNS_COVERED_SWITCH_DEFAULT:
+			break;
 	}
 
 	return encoded_str;
@@ -114,6 +116,9 @@
 			require_action(data_len < MAX_LENGTH_B32_HEX_ENCODING_DATA, exit, encoded_str_len = 0);
 			encoded_str_len = data_len / 5 * 8;
 			switch (data_len % 5) {
+				case 0:
+					// encoded_str_len += 0;
+					break;
 				case 1:
 					encoded_str_len += 2;
 					break;
@@ -126,8 +131,14 @@
 				case 4:
 					encoded_str_len += 7;
 					break;
+				MDNS_COVERED_SWITCH_DEFAULT:
+					encoded_str_len = 0;
+					break;
 			}
 			break;
+		MDNS_COVERED_SWITCH_DEFAULT:
+			encoded_str_len = 0;
+			break;
 	}
 
 exit:
diff --git a/mDNSShared/dns_objects/utilities/dns_common.c b/mDNSShared/dns_objects/utilities/dns_common.c
index 6671e68..0153d39 100644
--- a/mDNSShared/dns_objects/utilities/dns_common.c
+++ b/mDNSShared/dns_objects/utilities/dns_common.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Apple Inc. All rights reserved.
+ * Copyright (c) 2022-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -198,8 +198,8 @@
 		case kDNSRecordType_TA:			return("TA");
 		case kDNSRecordType_DLV:		return("DLV");
 		case kDNSRecordType_Reserved:	return("Reserved");
+		default:						return NULL;
 	}
-	return NULL;
 }
 
 //======================================================================================================================
diff --git a/mDNSShared/dns_objects/utilities/dns_obj_crypto.c b/mDNSShared/dns_objects/utilities/dns_obj_crypto.c
index 9081c8c..b33e1ab 100644
--- a/mDNSShared/dns_objects/utilities/dns_obj_crypto.c
+++ b/mDNSShared/dns_objects/utilities/dns_obj_crypto.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2022 Apple Inc. All rights reserved.
+ * Copyright (c) 2022-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -73,6 +73,7 @@
 			algorithm = kCCDigestSHA512;
 			break;
 		case DIGEST_UNSUPPORTED:
+		MDNS_COVERED_SWITCH_DEFAULT:
 			err = DNSSEC_ERROR_UNSUPPORTED_ERR;
 			algorithm = kCCDigestNone;
 			break;
@@ -149,6 +150,7 @@
 			algorithm = kCCDigestSHA512;
 			break;
 		case DIGEST_UNSUPPORTED:
+		MDNS_COVERED_SWITCH_DEFAULT:
 			err = DNSSEC_ERROR_UNSUPPORTED_ERR;
 			algorithm = kCCDigestNone;
 			break;
@@ -205,6 +207,7 @@
 			algorithm = kCCDigestSHA512;
 			break;
 		case DIGEST_UNSUPPORTED:
+		MDNS_COVERED_SWITCH_DEFAULT:
 			err = DNSSEC_ERROR_UNSUPPORTED_ERR;
 			algorithm = kCCDigestNone;
 			break;
@@ -386,7 +389,7 @@
 	SecKeyRef ecdsa_key = NULL;
 
 	const uint8_t const_four = 4;
-	check_compile_time_code(sizeof(const_four) == 1);
+	mdns_compile_time_check_local(sizeof(const_four) == 1);
 	key_cfdata = CFDataCreateMutable(kCFAllocatorDefault, (CFIndex)(sizeof(const_four) + key_size));
 	require_action(key_cfdata != NULL, exit, err = DNSSEC_ERROR_NO_MEMORY);
 
@@ -396,7 +399,7 @@
 
 	const void *key_options_keys[]		= {kSecAttrKeyType,					kSecAttrKeyClass};
 	const void *key_options_values[]	= {kSecAttrKeyTypeECSECPrimeRandom,	kSecAttrKeyClassPublic};
-	check_compile_time_code(countof(key_options_keys) == countof(key_options_values));
+	mdns_compile_time_check_local(countof(key_options_keys) == countof(key_options_values));
 	key_options_cfdictionary = CFDictionaryCreate(kCFAllocatorDefault, key_options_keys, key_options_values,
 		countof(key_options_keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
 	require_action(key_options_cfdictionary != NULL, exit, err = DNSSEC_ERROR_NO_MEMORY);
@@ -516,7 +519,7 @@
 			break;
 		case DNSKEY_ALGORITHM_ECDSAP256SHA256:
 		case DNSKEY_ALGORITHM_ECDSAP384SHA384:
-			*out_sec_key_algorithm = kSecKeyAlgorithmECDSASignatureRFC4754;
+			*out_sec_key_algorithm = kSecKeyAlgorithmECDSASignatureDigestRFC4754;
 			break;
 		default:
 			err = DNSSEC_ERROR_UNSUPPORTED_ERR;
diff --git a/mDNSShared/dns_sd.h b/mDNSShared/dns_sd.h
index 80a24fc..0604a17 100644
--- a/mDNSShared/dns_sd.h
+++ b/mDNSShared/dns_sd.h
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2003-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2003-2024 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -786,8 +786,8 @@
     kDNSServiceErr_Timeout                   = -65568,
     kDNSServiceErr_DefunctConnection         = -65569,  /* Connection to daemon returned a SO_ISDEFUNCT error result */
     kDNSServiceErr_PolicyDenied              = -65570,
-    kDNSServiceErr_NotPermitted              = -65571
-
+    kDNSServiceErr_NotPermitted              = -65571,
+    kDNSServiceErr_StaleData                 = -65572
 
                                                /* mDNS Error codes are in the range
                                                 * FFFE FF00 (-65792) to FFFE FFFF (-65537) */
@@ -967,10 +967,11 @@
  */
 
 #define kDNSServiceInterfaceIndexAny 0
-#define kDNSServiceInterfaceIndexLocalOnly ((uint32_t)-1)
-#define kDNSServiceInterfaceIndexUnicast   ((uint32_t)-2)
-#define kDNSServiceInterfaceIndexP2P       ((uint32_t)-3)
-#define kDNSServiceInterfaceIndexBLE       ((uint32_t)-4)
+#define kDNSServiceInterfaceIndexLocalOnly ((uint32_t)0xffffffffU)
+#define kDNSServiceInterfaceIndexUnicast   ((uint32_t)0xfffffffeU)
+#define kDNSServiceInterfaceIndexP2P       ((uint32_t)0xfffffffdU)
+#define kDNSServiceInterfaceIndexBLE       ((uint32_t)0xfffffffcU)
+#define kDNSServiceInterfaceIndexInfra     ((uint32_t)0xfffffffbU) // Reserved, not used by DNSService API
 
 typedef uint32_t DNSServiceFlags;
 typedef uint32_t DNSServiceProtocol;
@@ -3115,6 +3116,7 @@
 /*!
  *  @brief
  *                  Set the timestamp value in attr.
+ *                  The host key hash must also be set in attr.
  *
  *  @param attr
  *                  DNSServiceAttribute pointer.
@@ -3133,6 +3135,25 @@
 
 /*!
  *  @brief
+ *                  Set the host key hash value in attr.
+ *                  The timestamp attribute must also be set in attr.
+ *
+ *  @param attr
+ *                  DNSServiceAttribute pointer.
+ *  @param hostkeyhash
+ *                  A 32-bit host key hash value.
+ *  @result
+ *                  Returns kDNSServiceErr_NoError on success.
+ */
+DNSSD_EXPORT
+DNSServiceErrorType DNSSD_API DNSServiceAttributeSetHostKeyHash
+(
+    DNSServiceAttributeRef DNS_SD_NONNULL attr,
+    uint32_t hostkeyhash
+);
+
+/*!
+ *  @brief
  *                  Free DNSServiceAttribute pointer pointed by attr,
  *
  *  @param attr
diff --git a/mDNSShared/dns_sd_private.h b/mDNSShared/dns_sd_private.h
index ec0fefd..f77d5f4 100644
--- a/mDNSShared/dns_sd_private.h
+++ b/mDNSShared/dns_sd_private.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015-2022 Apple Inc. All rights reserved.
+ * Copyright (c) 2015-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/mDNSShared/dnssd_clientstub.c b/mDNSShared/dnssd_clientstub.c
index 29adb49..fc93d16 100644
--- a/mDNSShared/dnssd_clientstub.c
+++ b/mDNSShared/dnssd_clientstub.c
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2003-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2003-2024 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -247,7 +247,9 @@
 
 struct DNSServiceAttribute_s {
     DNSServiceAAAAPolicy aaaa_policy;
+    uint32_t hostkeyhash;
     uint32_t timestamp;    //Timestamp in seconds since epoch time to indicate when the service/record is registered.
+    bool hostkeyhash_is_set;
     bool timestamp_is_set;
 };
 
@@ -305,6 +307,17 @@
 }
 
 
+DNSServiceErrorType DNSSD_API DNSServiceAttributeSetHostKeyHash
+(
+    const DNSServiceAttributeRef attr,
+    uint32_t host_key
+)
+{
+    attr->hostkeyhash_is_set = true;
+    attr->hostkeyhash = host_key;
+    return kDNSServiceErr_NoError;
+}
+
 DNSServiceErrorType DNSSD_API DNSServiceAttributeSetTimestamp
 (
     const DNSServiceAttributeRef attr,
@@ -326,30 +339,47 @@
     mdns_free(tmp);
 }
 
-size_t
-get_required_tlv_length_for_service_attr(const DNSServiceAttribute * const attr)
+static bool
+validate_attribute_tlvs(const DNSServiceAttribute * const attr)
 {
-    // Length for IPC_TLV_TYPE_SERVICE_ATTR_AAAA_POLICY and IPC_TLV_TYPE_SERVICE_ATTR_FAILOVER_POLICY.
-    size_t len = 2 * get_required_tlv_uint32_length();
-
-    // IPC_TLV_TYPE_SERVICE_ATTR_TIMESTAMP.
-    if (attr->timestamp_is_set)
+    if (!attr)
     {
-        len += get_required_tlv_uint32_length();
+        return true;
     }
-    return len;
+    // If either is set, require both
+    if ((attr->timestamp_is_set || attr->hostkeyhash_is_set) &&
+        (!attr->timestamp_is_set || !attr->hostkeyhash_is_set))
+    {
+        return false;
+    }
+    return true;
 }
 
-void
-put_tlvs_for_service_attr(const DNSServiceAttribute * const attr, ipc_msg_hdr * const hdr, uint8_t ** const ptr,
-                          const uint8_t * const limit)
+static size_t
+put_attribute_tlvs(const DNSServiceAttribute * const attr, ipc_msg_hdr * const hdr, uint8_t ** const ptr,
+    const uint8_t * const limit)
 {
-    put_tlv_uint32(IPC_TLV_TYPE_SERVICE_ATTR_AAAA_POLICY, attr->aaaa_policy, ptr, limit);
+    size_t required_len = 0;
+    required_len += put_tlv_uint32(IPC_TLV_TYPE_SERVICE_ATTR_AAAA_POLICY, attr->aaaa_policy, ptr, limit);
     if (attr->timestamp_is_set)
     {
-        put_tlv_uint32(IPC_TLV_TYPE_SERVICE_ATTR_TIMESTAMP, attr->timestamp, ptr, limit);
+        required_len += put_tlv_uint32(IPC_TLV_TYPE_SERVICE_ATTR_TIMESTAMP, attr->timestamp, ptr, limit);
     }
-    hdr->ipc_flags |= IPC_FLAGS_TRAILING_TLVS;
+    if (attr->hostkeyhash_is_set)
+    {
+        required_len += put_tlv_uint32(IPC_TLV_TYPE_SERVICE_ATTR_HOST_KEY_HASH, attr->hostkeyhash, ptr, limit);
+    }
+    if (hdr)
+    {
+        hdr->ipc_flags |= IPC_FLAGS_TRAILING_TLVS;
+    }
+    return required_len;
+}
+
+static size_t
+get_required_length_for_attribute_tlvs(const DNSServiceAttribute * const attr)
+{
+    return put_attribute_tlvs(attr, NULL, NULL, NULL);
 }
 
 static bool _should_return_noauth_error(void)
@@ -2011,7 +2041,14 @@
     len += strlen(name) + strlen(regtype) + strlen(domain) + strlen(host) + 4;
     len += 2 * sizeof(uint16_t);  // port, txtLen
     len += txtLen;
-    (void)attr;
+    if (attr)
+    {
+        if (!validate_attribute_tlvs(attr))
+        {
+            return kDNSServiceErr_BadParam;
+        }
+        len += get_required_length_for_attribute_tlvs(attr);
+    }
 
     hdr = create_hdr(reg_service_request, &len, &ptr, (*sdRef)->primary ? 1 : 0, *sdRef);
     if (!hdr) { DNSServiceRefDeallocate(*sdRef); *sdRef = NULL; return kDNSServiceErr_NoMemory; }
@@ -2027,6 +2064,10 @@
     *ptr++ = port.b[1];
     put_uint16(txtLen, &ptr);
     put_rdata(txtLen, txtRecord, &ptr);
+    if (attr)
+    {
+        put_attribute_tlvs(attr, hdr, &ptr, limit);
+    }
 
     err = deliver_request(hdr, *sdRef);     // Will free hdr for us
     if (err == kDNSServiceErr_NoAuth && !_should_return_noauth_error())
@@ -2326,7 +2367,14 @@
     len += 3 * sizeof(uint16_t);  // rrtype, rrclass, rdlen
     len += strlen(fullname) + 1;
     len += rdlen;
-    (void)attr;
+    if (attr)
+    {
+        if (!validate_attribute_tlvs(attr))
+        {
+            return kDNSServiceErr_BadParam;
+        }
+        len += get_required_length_for_attribute_tlvs(attr);
+    }
 
     // Bump up the uid. Normally for shared operations (kDNSServiceFlagsShareConnection), this
     // is done in ConnectToServer. For DNSServiceRegisterRecord, ConnectToServer has already
@@ -2349,6 +2397,10 @@
     put_uint16(rdlen, &ptr);
     put_rdata(rdlen, rdata, &ptr);
     put_uint32(ttl, &ptr);
+    if (attr)
+    {
+        put_attribute_tlvs(attr, hdr, &ptr, limit);
+    }
     if (flags & kDNSServiceFlagsQueueRequest)
     {
         hdr->ipc_flags |= IPC_FLAGS_NOERRSD;
@@ -2493,7 +2545,11 @@
     len += sizeof(DNSServiceFlags);
     if (attr)
     {
-        len += get_required_tlv_length_for_service_attr(attr);
+        if (!validate_attribute_tlvs(attr))
+        {
+            return kDNSServiceErr_BadParam;
+        }
+        len += get_required_length_for_attribute_tlvs(attr);
     }
 
     hdr = create_hdr(update_record_request, &len, &ptr, 1, sdRef);
@@ -2520,7 +2576,7 @@
     put_uint32(ttl, &ptr);
     if (attr)
     {
-        put_tlvs_for_service_attr(attr, hdr, &ptr, limit);
+        put_attribute_tlvs(attr, hdr, &ptr, limit);
     }
     return deliver_request(hdr, sdRef);     // Will free hdr for us
 }
diff --git a/mDNSShared/dnssd_clientstub.h b/mDNSShared/dnssd_clientstub.h
index 9b0dad8..94cbe08 100644
--- a/mDNSShared/dnssd_clientstub.h
+++ b/mDNSShared/dnssd_clientstub.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021-2022 Apple Inc. All rights reserved.
+ * Copyright (c) 2021-2024 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -60,9 +60,3 @@
 
 DNSServiceErrorType
 DNSServiceSendQueuedRequestsInternal(DNSServiceRef sdr);
-
-size_t
-get_required_tlv_length_for_service_attr(const DNSServiceAttribute *attr);
-
-void
-put_tlvs_for_service_attr(const DNSServiceAttribute *attr, ipc_msg_hdr *hdr, uint8_t **ptr, const uint8_t *limit);
diff --git a/mDNSShared/dnssd_errstring.c b/mDNSShared/dnssd_errstring.c
index e3c7fea..5512037 100644
--- a/mDNSShared/dnssd_errstring.c
+++ b/mDNSShared/dnssd_errstring.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 Apple Inc. All rights reserved.
+ * Copyright (c) 2021-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -57,8 +57,9 @@
 		CASE_TO_STR(DefunctConnection)
 		CASE_TO_STR(PolicyDenied)
 		CASE_TO_STR(NotPermitted)
+		default:
+			return "<INVALID ERROR CODE>";
 	}
-	return "<INVALID ERROR CODE>";
 #undef CASE_TO_STR
 }
 
diff --git a/mDNSShared/dnssd_ipc.c b/mDNSShared/dnssd_ipc.c
index e192413..e9cbdbf 100644
--- a/mDNSShared/dnssd_ipc.c
+++ b/mDNSShared/dnssd_ipc.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2003-2024 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -214,14 +214,15 @@
     return (IPC_TLV16_OVERHEAD_LENGTH + 4);
 }
 
-static int _tlv16_set(uint8_t *const dst, const uint8_t *const limit, const uint16_t type, const uint16_t length,
+static size_t _tlv16_set(uint8_t *const dst, const uint8_t *const limit, const uint16_t type, const uint16_t length,
     const uint8_t *const value, uint8_t **const out_end)
 {
-    if ((limit - dst) < (IPC_TLV16_OVERHEAD_LENGTH + length))
-    {
-        return -1;
-    }
     uint8_t *ptr = dst;
+    const size_t required_len = IPC_TLV16_OVERHEAD_LENGTH + length;
+    mdns_require_quiet(ptr, exit);
+    mdns_require_quiet(ptr <= limit, exit);
+    mdns_require_quiet((size_t)(limit - ptr) >= required_len, exit);
+
     ptr = _write_big16(ptr, type);
     ptr = _write_big16(ptr, length);
     if (length > 0)
@@ -229,18 +230,17 @@
         memcpy(ptr, value, length);
         ptr += length;
     }
-    _assign_null_safe(out_end, ptr);
-    return 0;
+
+exit:
+    mdns_assign(out_end, ptr);
+    return required_len;
 }
 
-void put_tlv(const uint16_t type, const uint16_t length, const uint8_t *const value, uint8_t **const ptr,
+size_t put_tlv(const uint16_t type, const uint16_t length, const uint8_t *const value, uint8_t **const ptr,
     const uint8_t *const limit)
 {
-    uint8_t *dst = *ptr;
-    if (_tlv16_set(dst, limit, type, length, value, &dst) == 0)
-    {
-        *ptr = dst;
-    }
+    uint8_t *const dst = ptr ? *ptr : NULL;
+    return _tlv16_set(dst, limit, type, length, value, ptr);
 }
 
 void put_tlv_string(const uint16_t type, const char *const str_value, uint8_t **const ptr, const uint8_t *const limit,
@@ -268,11 +268,17 @@
     put_tlv(type, sizeof(value), value, ptr, limit);
 }
 
-void put_tlv_uint32(const uint16_t type, const uint32_t u32, uint8_t **const ptr, const uint8_t *const limit)
+size_t put_tlv_uint32(const uint16_t type, const uint32_t u32, uint8_t **const ptr, const uint8_t *const limit)
 {
     uint8_t value[4];
     _write_big32(value, u32);
-    put_tlv(type, sizeof(value), value, ptr, limit);
+    return put_tlv(type, sizeof(value), value, ptr, limit);
+}
+
+size_t put_tlv_uuid(const uint16_t type, const uint8_t uuid[MDNS_STATIC_ARRAY_PARAM MDNS_UUID_SIZE], uint8_t **const ptr,
+    const uint8_t *const limit)
+{
+    return put_tlv(type, MDNS_UUID_SIZE, uuid, ptr, limit);
 }
 
 static const uint8_t *_tlv16_get_next(const uint8_t *ptr, const uint8_t *const end, uint16_t *const out_type,
@@ -353,12 +359,28 @@
                 u32 = _read_big32(value, NULL);
                 err = 0;
                 break;
+            default:
+                break;
         }
     }
     _assign_null_safe(out_error, err);
     return u32;
 }
 
+const uint8_t *get_tlv_uuid(const uint8_t *const start, const uint8_t *const end, const uint16_t type)
+{
+    const uint8_t *uuid = NULL;
+    size_t length = 0;
+    const uint8_t *const value = get_tlv(start, end, type, &length);
+    mdns_require_quiet(value, exit);
+    mdns_require_quiet(length == MDNS_UUID_SIZE, exit);
+
+    uuid = value;
+
+exit:
+    return uuid;
+}
+
 void ConvertHeaderBytes(ipc_msg_hdr *hdr)
 {
     hdr->version   = htonl(hdr->version);
diff --git a/mDNSShared/dnssd_ipc.h b/mDNSShared/dnssd_ipc.h
index a0b6f21..d5d396c 100644
--- a/mDNSShared/dnssd_ipc.h
+++ b/mDNSShared/dnssd_ipc.h
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2003-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2003-2024 Apple Inc. All rights reserved.
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions are met:
@@ -30,6 +30,7 @@
 #define DNSSD_IPC_H
 
 #include "dns_sd.h"
+#include "general.h"
 
 //
 // Common cross platform services
@@ -117,6 +118,13 @@
 #define IPC_TLV_TYPE_GET_TRACKER_STR                8 // A uint8. If non-zero, include tracker domain if applicable.
 #define IPC_TLV_TYPE_SERVICE_ATTR_TRACKER_STR       9 // A null-terminated string. The domain (original hostname or resolved CNAME)
                                                       // that was identified as a tracker
+#define IPC_TLV_TYPE_RESOLVER_OVERRIDE             10 // UUID of resolver configuration to use as an override. [1]
+#define IPC_TLV_TYPE_SERVICE_ATTR_HOST_KEY_HASH    11 // A uint32 value for a host key hash.
+
+// Notes:
+// 1. If this TLV is specified, then the UUID identifies the libnetwork resolver configuration to use for the DNS-SD
+//    API operation (currently limited to DNSServiceQueryRecordWithAttribute()). This overrides the normal DNS service
+//    select process.
 
 // Structure packing macro. If we're not using GNUC, it's not fatal. Most compilers naturally pack the on-the-wire
 // structures correctly anyway, so a plain "struct" is usually fine. In the event that structures are not packed
@@ -228,15 +236,18 @@
 size_t get_required_tlv_string_length(const char *str_value);
 size_t get_required_tlv_uint8_length(void);
 size_t get_required_tlv_uint32_length(void);
-void put_tlv(uint16_t type, uint16_t length, const uint8_t *value, uint8_t **ptr, const uint8_t *limit);
+size_t put_tlv(uint16_t type, uint16_t length, const uint8_t *value, uint8_t **ptr, const uint8_t *limit);
 void put_tlv_string(const uint16_t type, const char *const str_value, uint8_t **const ptr, const uint8_t *const limit,
     int *const out_error);
 void put_tlv_uint8(uint16_t type, uint8_t u8, uint8_t **ptr, const uint8_t *limit);
 void put_tlv_uint16(uint16_t type, uint16_t u16, uint8_t **ptr, const uint8_t *limit);
-void put_tlv_uint32(uint16_t type, uint32_t u32, uint8_t **ptr, const uint8_t *limit);
+size_t put_tlv_uint32(uint16_t type, uint32_t u32, uint8_t **ptr, const uint8_t *limit);
+size_t put_tlv_uuid(uint16_t type, const uint8_t uuid[MDNS_STATIC_ARRAY_PARAM MDNS_UUID_SIZE], uint8_t **ptr,
+    const uint8_t *limit);
 const uint8_t *get_tlv(const uint8_t *src, const uint8_t *end, uint16_t type, size_t *out_length);
 const char *get_tlv_string(const uint8_t *const start, const uint8_t *const end, const uint16_t type);
 uint32_t get_tlv_uint32(const uint8_t *src, const uint8_t *end, uint16_t type, int *out_error);
+const uint8_t *get_tlv_uuid(const uint8_t *src, const uint8_t *end, uint16_t type);
 
 void ConvertHeaderBytes(ipc_msg_hdr *hdr);
 
diff --git a/mDNSShared/general.h b/mDNSShared/general.h
index c4bf11b..c5fdf4d 100644
--- a/mDNSShared/general.h
+++ b/mDNSShared/general.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2023-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,6 +17,106 @@
 #ifndef MDNS_GENERAL_H
 #define MDNS_GENERAL_H
 
+/*!
+ *	@brief
+ *		Evaluates to non-zero if compiling for a particular platform.
+ *
+ *	@param PLATFORM_NAME
+ *		The name of the platform, e.g., APPLE.
+ */
+#define MDNS_PLATFORM(PLATFORM_NAME)	MDNS_PLATFORM_PRIVATE_DEFINITION_ ## PLATFORM_NAME ()
+
+/*!
+ *	@brief
+ *		Evaluates to non-zero if compiling for Apple OSes.
+ *
+ *	@discussion
+ *		`__APPLE__` is defined when compiling for Apple, see
+ *		<https://developer.apple.com/library/archive/documentation/Porting/Conceptual/PortingUnix/compiling/compiling.html>.
+ */
+#if defined(__APPLE__) && __APPLE__
+	#define MDNS_PLATFORM_PRIVATE_DEFINITION_APPLE()	1
+#else
+	#define MDNS_PLATFORM_PRIVATE_DEFINITION_APPLE()	0
+#endif
+
+#if MDNS_PLATFORM(APPLE)
+	#include <TargetConditionals.h>
+#endif
+
+/*!
+ *	@brief
+ *		Evaluates to non-zero if compiling for a particular OS.
+ *
+ *	@param OS_NAME
+ *		The name of the OS, e.g., macOS, iOS, etc.
+ */
+#define MDNS_OS(OS_NAME)	MDNS_OS_PRIVATE_DEFINITION_ ## OS_NAME ()
+
+/*!
+ *	@brief
+ *		Evaluates to non-zero if compiling for macOS.
+ *
+ *	@discussion
+ *		Use `MDNS_OS(macOS)` instead of using this macro directly.
+ */
+#if defined(TARGET_OS_OSX) && TARGET_OS_OSX
+	#define MDNS_OS_PRIVATE_DEFINITION_macOS()	1
+#else
+	#define MDNS_OS_PRIVATE_DEFINITION_macOS()	0
+#endif
+
+/*!
+ *	@brief
+ *		Evaluates to non-zero if compiling for iOS.
+ *
+ *	@discussion
+ *		Use `MDNS_OS(iOS)` instead of using this macro directly.
+ */
+#if defined(TARGET_OS_IOS) && TARGET_OS_IOS
+	#define MDNS_OS_PRIVATE_DEFINITION_iOS()	1
+#else
+	#define MDNS_OS_PRIVATE_DEFINITION_iOS()	0
+#endif
+
+/*!
+ *	@brief
+ *		Evaluates to non-zero if compiling for watchOS.
+ *
+ *	@discussion
+ *		Use `MDNS_OS(watchOS)` instead of using this macro directly.
+ */
+#if defined(TARGET_OS_WATCH) && TARGET_OS_WATCH
+	#define MDNS_OS_PRIVATE_DEFINITION_watchOS()	1
+#else
+	#define MDNS_OS_PRIVATE_DEFINITION_watchOS()	0
+#endif
+
+/*!
+ *	@brief
+ *		Evaluates to non-zero if compiling for tvOS.
+ *
+ *	@discussion
+ *		Use `MDNS_OS(tvOS)` instead of using this macro directly.
+ */
+#if defined(TARGET_OS_TV) && TARGET_OS_TV
+	#define MDNS_OS_PRIVATE_DEFINITION_tvOS()	1
+#else
+	#define MDNS_OS_PRIVATE_DEFINITION_tvOS()	0
+#endif
+
+// Time conversion constants
+
+#define MDNS_NANOSECONDS_PER_SECOND		1000000000
+#define MDNS_MILLISECONDS_PER_SECOND	1000
+#define MDNS_MILLISECONDS_PER_MINUTE	(MDNS_MILLISECONDS_PER_SECOND * MDNS_SECONDS_PER_MINUTE)
+#define MDNS_MILLISECONDS_PER_HOUR		(MDNS_MILLISECONDS_PER_SECOND * MDNS_SECONDS_PER_HOUR)
+#define MDNS_SECONDS_PER_MINUTE			60
+#define MDNS_SECONDS_PER_HOUR			(MDNS_SECONDS_PER_MINUTE * MDNS_MINUTES_PER_HOUR)
+#define MDNS_SECONDS_PER_DAY			(MDNS_SECONDS_PER_HOUR * MDNS_HOUR_PER_DAY)
+#define MDNS_MINUTES_PER_HOUR			60
+#define MDNS_HOUR_PER_DAY				24
+
 // Clang's __has_*() builtin macros are defined as zero if not defined.
 
 #if !defined(__has_attribute)
@@ -179,6 +279,38 @@
 
 /*!
  *	@brief
+ *		For Clang, starts ignoring the -Wincompatible-function-pointer-types-strict warning diagnostic flag.
+ *
+ *	@discussion
+ *		-Wincompatible-function-pointer-types-strict is like -Wincompatible-function-pointer-types, but is more
+ *		strict in that it warns about function pointer types that are not identical but are still compatible.
+ *
+ *		The -Wincompatible-function-pointer-types-strict is new in clang version 16.0.0 (see
+ *		https://releases.llvm.org/16.0.0/tools/clang/docs/ReleaseNotes.html). This macro allow us to
+ *		conditionally ignore -Wincompatible-function-pointer-types-strict with Clang 16.0.0 or later. This
+ *		avoids -Wunknown-warning-option warnings with earlier Clang versions, which don't recognize
+ *		-Wincompatible-function-pointer-types-strict.
+ */
+#if MDNS_CLANG_VERSION_IS_AT_LEAST(16, 0, 0)
+	#define MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_BEGIN() \
+		MDNS_CLANG_IGNORE_WARNING_BEGIN(-Wincompatible-function-pointer-types-strict)
+#else
+	#define MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_BEGIN()
+#endif
+
+/*!
+ *	@brief
+ *		Undoes the effect of a previous
+ *		MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_BEGIN().
+ */
+#if MDNS_CLANG_VERSION_IS_AT_LEAST(16, 0, 0)
+	#define MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_END()	MDNS_CLANG_IGNORE_WARNING_END()
+#else
+	#define MDNS_CLANG_IGNORE_INCOMPATIBLE_FUNCTION_POINTER_TYPES_STRICT_WARNING_END()
+#endif
+
+/*!
+ *	@brief
  *		For Clang, treats the specified warning diagnostic flag as an error.
  *
  *	@param WARNING
@@ -342,6 +474,72 @@
 
 /*!
  *	@brief
+ *		Evaluates to non-zero if the compiler conforms to a specific minimum C standard.
+ *
+ *	@param STANDARD
+ *		The C standard.
+ */
+#define MDNS_C_STANDARD_IS_AT_LEAST(STANDARD)	MDNS_C_STANDARD_PRIVATE_DEFINITION_IS_AT_LEAST_ ## STANDARD ()
+
+/*!
+ *	@brief
+ *		Evaluates to non-zero if the compiler confroms to the C99 standard or later.
+ *
+ *	@discussion
+ *		__STDC_VERSION__ is a predefined macro that expands to 199901L for the C99 standard. See
+ *		<https://en.cppreference.com/w/c/preprocessor/replace>.
+ *
+ *		Use `MDNS_C_STANDARD_IS_AT_LEAST(C99)` instead of using this macro directly.
+ */
+#if defined(__STDC_VERSION__)
+	#define MDNS_C_STANDARD_PRIVATE_DEFINITION_IS_AT_LEAST_C99()	(__STDC_VERSION__ >= 199901L)
+#else
+	#define MDNS_C_STANDARD_PRIVATE_DEFINITION_IS_AT_LEAST_C99()	0
+#endif
+
+/*!
+ *	@brief
+ *		Evaluates to non-zero if the compiler confroms to the C11 standard or later.
+ *
+ *	@discussion
+ *		__STDC_VERSION__ is a predefined macro that expands to 201112L for the C11 standard. See
+ *		<https://en.cppreference.com/w/c/preprocessor/replace>.
+ *
+ *		Use `MDNS_C_STANDARD_IS_AT_LEAST(C11)` instead of using this macro directly.
+ */
+#if defined(__STDC_VERSION__)
+	#define MDNS_C_STANDARD_PRIVATE_DEFINITION_IS_AT_LEAST_C11()	(__STDC_VERSION__ >= 201112L)
+#else
+	#define MDNS_C_STANDARD_PRIVATE_DEFINITION_IS_AT_LEAST_C11()	0
+#endif
+
+/*!
+ *	@brief
+ *		Evaluates to non-zero if the compiler conforms to a specific minimum C++ standard.
+ *
+ *	@param STANDARD
+ *		The C standard.
+ */
+#define MDNS_CPP_STANDARD_IS_AT_LEAST(STANDARD)	MDNS_CPP_STANDARD_PRIVATE_DEFINITION_IS_AT_LEAST_ ## STANDARD ()
+
+/*!
+ *	@brief
+ *		Evaluates to non-zero if the compiler confroms to the C++11 standard or later.
+ *
+ *	@discussion
+ *		__cplusplus is a predefined macro that expands to 201103L for the C++11 standard. See
+ *		<https://en.cppreference.com/w/cpp/preprocessor/replace>.
+ *
+ *		Use `MDNS_CPP_STANDARD_IS_AT_LEAST(CPP11)` instead of using this macro directly.
+ */
+#if defined(__cplusplus)
+	#define MDNS_CPP_STANDARD_PRIVATE_DEFINITION_IS_AT_LEAST_CPP11()	(__cplusplus >= 201103L)
+#else
+	#define MDNS_CPP_STANDARD_PRIVATE_DEFINITION_IS_AT_LEAST_CPP11()	0
+#endif
+
+/*!
+ *	@brief
  *		Causes a compile-time error if an expression evaluates to false.
  *
  *	@param EXPRESSION
@@ -350,9 +548,9 @@
  *	@param MESSAGE
  *		If supported, a sting literal to include as a diagnostic message if the expression evaluates to false.
  */
-#if (defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 201112L))
+#if MDNS_C_STANDARD_IS_AT_LEAST(C11)
 	#define mdns_compile_time_check(EXPRESSION, MESSAGE)	_Static_assert(EXPRESSION, MESSAGE)
-#elif (defined(__cplusplus) && (__cplusplus >= 201103L))
+#elif MDNS_CPP_STANDARD_IS_AT_LEAST(CPP11)
 	#define mdns_compile_time_check(EXPRESSION, MESSAGE)	static_assert(EXPRESSION, MESSAGE)
 #elif defined(__cplusplus)
 	#define	mdns_compile_time_check(EXPRESSION, MESSAGE) \
@@ -364,6 +562,33 @@
 
 /*!
  *	@brief
+ *		Causes a compile-time error if an expression evaluates to false.
+ *
+ *	@param EXPRESSION
+ *		The expression.
+ *
+ *	@discussion
+ *		This macro is meant to be used in a local scope, i.e., inside of a function or a block. For the global
+ *		scope, use `mdns_compile_time_check()`.
+ *
+ *		The fallback implementation is based on code from
+ *		<https://www.drdobbs.com/compile-time-assertions/184401873>.
+ */
+#if MDNS_C_STANDARD_IS_AT_LEAST(C11)
+	#define mdns_compile_time_check_local(EXPRESSION)	_Static_assert(EXPRESSION, "Compile-time assertion failed.")
+#elif MDNS_CPP_STANDARD_IS_AT_LEAST(CPP11)
+	#define mdns_compile_time_check_local(EXPRESSION)	static_assert(EXPRESSION, "Compile-time assertion failed.")
+#else
+	#define mdns_compile_time_check_local(EXPRESSION)								\
+		do {																		\
+			enum {																	\
+				mdns_compile_time_check_local_failed = 1 / ((EXPRESSION) ? 1 : 0)	\
+			};																		\
+		} while (0)
+#endif
+
+/*!
+ *	@brief
  *		Determines at compile-time if the size of a type exceeds a specified maximum.
  *
  *	@param TYPE
@@ -500,6 +725,29 @@
 
 /*!
  *	@brief
+ *		If an expression evaluates to false, executes an action, then returns.
+ *
+ *	@param EXPRESSION
+ *		The expression.
+ *
+ *	@param ACTION
+ *		The code to execute.
+ *
+ *	@discussion
+ *		No debugging information is logged.
+ */
+#define mdns_require_return_action(EXPRESSION, ACTION)	\
+	do {												\
+		if (!(EXPRESSION)) {							\
+			{											\
+				ACTION;									\
+			}											\
+			return;										\
+		}												\
+	} while (0)
+
+/*!
+ *	@brief
  *		Returns from the current function with a specified value if an expression evaluates to false.
  *
  *	@param EXPRESSION
@@ -601,4 +849,86 @@
 	mdns_compile_time_check(mdns_sizeof_member(STRUCT_TYPE, _mdns_unused_padding) < _Alignof(STRUCT_TYPE),	\
 		"Padding exceeds alignment of '" # STRUCT_TYPE "', so the amount of padding is excessive.")
 
+/*!
+ *	@brief
+ *		Retains a Core Foundation object if the specified object reference is non-NULL.
+ *
+ *	@param OBJ
+ *		A reference to the object to retain.
+ *
+ *	@discussion
+ *		The object reference is explicitly compared against NULL to avoid a warning from the Clang analyzer's
+ *		osx.NumberObjectConversion checker. See
+ *		<https://clang.llvm.org/docs/analyzer/checkers.html#osx-numberobjectconversion-c-c-objc>.
+ */
+#define mdns_cf_retain_null_safe(OBJ)	\
+	do {								\
+		if ((OBJ) != NULL) {			\
+			CFRetain((OBJ));			\
+		}								\
+	} while (0)
+
+/*!
+ *	@brief
+ *		Releases the Core Foundation object referenced by a pointer.
+ *
+ *	@param OBJ_PTR
+ *		The address of the pointer that either references a Core Foundation object or references NULL.
+ *
+ *	@discussion
+ *		If the pointer contains a non-NULL reference, then the pointer will be set to NULL after releasing the
+ *		object.
+ *
+ *		The object reference is explicitly compared against NULL to avoid a warning from the Clang analyzer's
+ *		osx.NumberObjectConversion checker. See
+ *		<https://clang.llvm.org/docs/analyzer/checkers.html#osx-numberobjectconversion-c-c-objc>.
+ */
+#define mdns_cf_forget(OBJ_PTR)		\
+	do {							\
+		if (*(OBJ_PTR) != NULL) {	\
+			CFRelease(*(OBJ_PTR));	\
+			*(OBJ_PTR) = NULL;		\
+		}							\
+	} while (0)
+
+/*!
+ *	@brief
+ *		Alternative to the `default` label in a switch statement that covers all enumeration values.
+ *
+ *	@discussion
+ *		Use `MDNS_COVERED_SWITCH_DEFAULT` instead of `default` to avoid the `-Wcovered-switch-default` warning
+ *		in a switch statement that covers all enumeration values. This macro is useful when strict enforcement
+ *		of the `-Wswitch-default` warning compels us to include a default label in such switch statements.
+ */
+#if MDNS_COMPILER_IS_CLANG()
+	#define MDNS_COVERED_SWITCH_DEFAULT								\
+		MDNS_CLANG_IGNORE_WARNING_BEGIN(-Wcovered-switch-default)	\
+		default														\
+		MDNS_CLANG_IGNORE_WARNING_END()
+#else
+	#define MDNS_COVERED_SWITCH_DEFAULT	default
+#endif
+
+/*!
+ *	@brief
+ *		The static keyword for array parameters for C99 or later.
+ *
+ *	@discussion
+ *		See <https://en.cppreference.com/w/c/language/operator_other#Function_call>.
+ */
+#if MDNS_C_STANDARD_IS_AT_LEAST(C99)
+	#define MDNS_STATIC_ARRAY_PARAM	static
+#else
+	#define MDNS_STATIC_ARRAY_PARAM
+#endif
+
+/*!
+ *	@brief
+ *		The size of a Universally Unique Identifier (UUID) in bytes.
+ *
+ *	@discussion
+ *		See <https://datatracker.ietf.org/doc/html/rfc4122#section-4.1>.
+ */
+#define MDNS_UUID_SIZE	16
+
 #endif	// MDNS_GENERAL_H
diff --git a/mDNSShared/mDNSDebug.c b/mDNSShared/mDNSDebug.c
index 7a4ca19..b531fce 100644
--- a/mDNSShared/mDNSDebug.c
+++ b/mDNSShared/mDNSDebug.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003-2021 Apple Inc. All rights reserved.
+ * Copyright (c) 2003-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -30,6 +30,7 @@
 #include "mdns_strict.h"
 
 mDNSexport int mDNS_LoggingEnabled       = 0;
+mDNSexport int mDNS_DebugLoggingEnabled  = 0;
 mDNSexport int mDNS_PacketLoggingEnabled = 0;
 mDNSexport int mDNS_McastLoggingEnabled  = 0;
 mDNSexport int mDNS_McastTracingEnabled  = 0;
diff --git a/mDNSShared/mDNSFeatures.h b/mDNSShared/mDNSFeatures.h
index f816840..a225d35 100644
--- a/mDNSShared/mDNSFeatures.h
+++ b/mDNSShared/mDNSFeatures.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2019-2022 Apple Inc. All rights reserved.
+ * Copyright (c) 2019-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,6 +17,8 @@
 #ifndef __mDNSFeatures_h
 #define __mDNSFeatures_h
 
+#include "general.h"
+
 #define HAS_FEATURE_CAT(A, B)       A ## B
 #define HAS_FEATURE_CHECK_0         1
 #define HAS_FEATURE_CHECK_1         1
@@ -40,15 +42,11 @@
 #define MDNSRESPONDER_PLATFORM_COMMON       1
 
 // Feature: DNS Push
-// Radar:   <rdar://problem/23226275>
-// Enabled: Yes, for Apple.
+// Radar:   <rdar://119684505>
+// Enabled: No.
 
 #if !defined(MDNSRESPONDER_SUPPORTS_COMMON_DNS_PUSH)
-    #if MDNSRESPONDER_PLATFORM_APPLE
-        #define MDNSRESPONDER_SUPPORTS_COMMON_DNS_PUSH      1
-    #else
-        #define MDNSRESPONDER_SUPPORTS_COMMON_DNS_PUSH      0
-    #endif
+    #define MDNSRESPONDER_SUPPORTS_COMMON_DNS_PUSH 0
 #endif
 
 // Feature: DNS LLQ
@@ -75,4 +73,22 @@
     #endif
 #endif
 
+// Feature: DNS-SD Sleep Proxy Service (SPS) client support
+// Radar:   No known radar.
+// Enabled: Compiled for all platforms, except iOS and watchOS (rdar://112912605), and macOS (rdar://118002582).
+
+#if !defined(MDNSRESPONDER_SUPPORTS_COMMON_SPS_CLIENT)
+    #if MDNS_OS(iOS) || MDNS_OS(watchOS) || MDNS_OS(macOS)
+        #define MDNSRESPONDER_SUPPORTS_COMMON_SPS_CLIENT 0
+    #else
+        #define MDNSRESPONDER_SUPPORTS_COMMON_SPS_CLIENT 1
+    #endif
+#endif
+
+#if MDNSRESPONDER_SUPPORTS(COMMON, DNS_PUSH)
+    #if MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH)
+        #error "MDNSRESPONDER_SUPPORTS(COMMON, DNS_PUSH) and MDNSRESPONDER_SUPPORTS(APPLE, DNS_PUSH) shouldn't be enabled at the same time."
+    #endif
+#endif
+
 #endif  // __mDNSFeatures_h
diff --git a/mDNSShared/tls-keychain.h b/mDNSShared/tls-keychain.h
index 480a4ef..8f5fca7 100644
--- a/mDNSShared/tls-keychain.h
+++ b/mDNSShared/tls-keychain.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 Apple Inc. All rights reserved.
+ * Copyright (c) 2021-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -49,6 +49,7 @@
 #if __APPLE__
 	sec_protocol_metadata_t _Nonnull metadata;
 	sec_trust_t _Nonnull trust_ref;
+	bool trusts_alternative_server_certificates;
 #else // __APPLE__
 	uint8_t not_a_real_member;
 #endif // __APPLE__
@@ -69,6 +70,24 @@
 
 /*!
  *	@brief
+ *		Specify alternative trusted TLS certificates that can be used to perform trust evaluation.
+ *
+ *	@param certs
+ *		An array of TLS certificates as CFDataRef objects.
+ *
+ *	@result
+ *		kNoErr if the trusted server certificates were successfully updated. Otherwise, a non-zero error code to indicate why
+ *		the operation failed.
+ *
+ *	@discussion
+ *		Calling this function for the second time after the first call overrides the previously configured certificates.
+ *		Calling this function with NULL clears any alternative certificate that we have trusted before.
+ */
+OSStatus
+tls_cert_set_alternative_trusted_certificates(CFArrayRef _Nullable certs);
+
+/*!
+ *	@brief
  *		Given the context, verify if the current TLS certificate should be trusted or not.
  *
  *	@param context
@@ -159,16 +178,12 @@
  *	@param out_certificates
  *		A pointer to a CFArrayRef variable that can be used to return the retrieved SecCertificateRef array.
  *
- *	@param return_attributes
- *		A boolean value that determines whether it should return the attributes dictionary for the certificates or not.
- *
  *	@result
  *		errSecSuccess if the certificates on the iCloud keychain are found, errSecItemNotFound if the certificates are not found, otherwise an error code to indicate
  *		the error.
  */
 OSStatus
-keychain_certificates_copy(CF_RETURNS_RETAINED CFArrayRef * const _Nonnull out_certificates,
-						   bool return_attributes);
+keychain_certificates_copy(CF_RETURNS_RETAINED CFArrayRef * const _Nonnull out_certificates);
 
 /*!
  *	@brief
diff --git a/mDNSShared/uds_daemon.c b/mDNSShared/uds_daemon.c
index 27a07e4..e73bc11 100644
--- a/mDNSShared/uds_daemon.c
+++ b/mDNSShared/uds_daemon.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2003-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2003-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -47,6 +47,7 @@
 #endif
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
+#include "cf_support.h"
 #include "mDNSMacOSX.h"
 #include <os/feature_private.h>
 #endif
@@ -82,6 +83,10 @@
 #include "system_utilities.h"
 #endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
+#include <mdns/powerlog.h>
+#endif
+
 #include "mdns_strict.h"
 
 // User IDs 0-500 are system-wide processes, not actual users in the usual sense
@@ -106,6 +111,9 @@
 static dnssd_sock_t listenfd = dnssd_InvalidSocket;
 static request_state *all_requests = NULL;
 mDNSlocal void set_peer_pid(request_state *request);
+mDNSlocal mDNSu32 request_state_get_duration(const request_state *request);
+mDNSlocal mDNSBool requestShouldLogName(request_state *request);
+mDNSlocal mDNSBool requestShouldLogFullRequestInfo(request_state *request);
 mDNSlocal void LogMcastClientInfo(request_state *req);
 mDNSlocal void GetMcastClients(request_state *req);
 mDNSlocal mStatus update_record(AuthRecord *ar, mDNSu16 rdlen, const mDNSu8 *rdata, mDNSu32 ttl,
@@ -150,6 +158,141 @@
 #endif
 #endif
 
+//======================================================================================================================
+// MARK: - Log macro for request state logging.
+
+#define UDS_LOG_CLIENT_REQUEST_WITH_DURATION(CATEGORY, LEVEL, REQUEST, LOG_DURATION, FORMAT, ...)   \
+    do                                                                                              \
+    {                                                                                               \
+        if (LOG_DURATION)                                                                           \
+        {                                                                                           \
+            LogRedact(CATEGORY, LEVEL, FORMAT ", duration: " PUB_TIME_DUR,                          \
+                ##__VA_ARGS__, request_state_get_duration(REQUEST));                                \
+        }                                                                                           \
+        else                                                                                        \
+        {                                                                                           \
+            LogRedact(CATEGORY, LEVEL, FORMAT, ##__VA_ARGS__);                                      \
+        }                                                                                           \
+    }                                                                                               \
+    while(0)
+
+#define UDS_LOG_CLIENT_REQUEST_WITH_NAME_HASH_DURATION(CATEGORY, LEVEL, NAME_FOR_NAME_HASH, REQUEST,    \
+            LOG_DURATION, FORMAT, ...)                                                                  \
+    do                                                                                                  \
+    {                                                                                                   \
+        if ((void *)(NAME_FOR_NAME_HASH) != NULL)                                                       \
+        {                                                                                               \
+            UDS_LOG_CLIENT_REQUEST_WITH_DURATION(CATEGORY, LEVEL, (REQUEST), LOG_DURATION,              \
+                FORMAT "name hash: %x", ##__VA_ARGS__, mDNS_DomainNameFNV1aHash(NAME_FOR_NAME_HASH));   \
+        }                                                                                               \
+        else                                                                                            \
+        {                                                                                               \
+            UDS_LOG_CLIENT_REQUEST_WITH_DURATION(CATEGORY, LEVEL, (REQUEST), LOG_DURATION,              \
+                FORMAT, ##__VA_ARGS__);                                                                 \
+        }                                                                                               \
+    }                                                                                                   \
+    while(0)
+
+#define UDS_LOG_CLIENT_REQUEST(CATEGORY, LEVEL, OPERATION_STR, NAME_FOR_NAME_HASH, REQUEST, LOG_DURATION,   \
+            FORMAT, ...)                                                                                    \
+    do                                                                                                      \
+    {                                                                                                       \
+        if (requestShouldLogFullRequestInfo(REQUEST))                                                       \
+        {                                                                                                   \
+            UDS_LOG_CLIENT_REQUEST_WITH_NAME_HASH_DURATION(                                                 \
+                CATEGORY, LEVEL, (NAME_FOR_NAME_HASH), (REQUEST), (LOG_DURATION),                           \
+                "[R%u] " OPERATION_STR " -- "                                                               \
+                FORMAT ", flags: 0x%X, interface index: %d, client pid: %d (" PUB_S "), ",                  \
+                (REQUEST)->request_id, ##__VA_ARGS__, (REQUEST)->flags, (REQUEST)->interfaceIndex,          \
+                (REQUEST)->process_id, (REQUEST)->pid_name);                                                \
+        }                                                                                                   \
+        else                                                                                                \
+        {                                                                                                   \
+            UDS_LOG_CLIENT_REQUEST_WITH_NAME_HASH_DURATION(                                                 \
+                CATEGORY, LEVEL, (NAME_FOR_NAME_HASH), (REQUEST), (LOG_DURATION),                           \
+                "[R%u] " OPERATION_STR " -- ", (REQUEST)->request_id);                                      \
+        }                                                                                                   \
+    }                                                                                                       \
+    while(0)
+
+#define UDS_LOG_CLIENT_REQUEST_WITH_DNSSEC_INFO(CATEGORY, LEVEL, OPERATION_STR, NAME_FOR_NAME_HASH, REQUEST,    \
+            LOG_DURATION, DNSSEC_ENABLED, FORMAT, ...)                                                          \
+    do                                                                                                          \
+    {                                                                                                           \
+        if (DNSSEC_ENABLED)                                                                                     \
+        {                                                                                                       \
+            UDS_LOG_CLIENT_REQUEST(CATEGORY, LEVEL, OPERATION_STR, NAME_FOR_NAME_HASH, REQUEST, LOG_DURATION,   \
+                FORMAT ", DNSSEC enabled", ##__VA_ARGS__);                                                      \
+        }                                                                                                       \
+        else                                                                                                    \
+        {                                                                                                       \
+            UDS_LOG_CLIENT_REQUEST(CATEGORY, LEVEL, OPERATION_STR, NAME_FOR_NAME_HASH, REQUEST, LOG_DURATION,   \
+                FORMAT, ##__VA_ARGS__);                                                                         \
+        }                                                                                                       \
+    }                                                                                                           \
+    while (mDNSfalse)
+
+//======================================================================================================================
+// MARK: - Log macro for query record result event logging.
+
+#define UDS_LOG_RDATA_WITH_RID_QID(CATEGORY, LEVEL, RID, QID, RR_PTR, FORMAT, ...)                                  \
+    do                                                                                                              \
+    {                                                                                                               \
+        if ((QID) != 0)                                                                                             \
+        {                                                                                                           \
+            MDNS_CORE_LOG_RDATA(CATEGORY, LEVEL, RR_PTR, "[R%u->Q%u] " FORMAT ", ", (RID), (QID), ##__VA_ARGS__);   \
+        }                                                                                                           \
+        else                                                                                                        \
+        {                                                                                                           \
+            MDNS_CORE_LOG_RDATA(CATEGORY, LEVEL, RR_PTR, "[R%u->mDNSQ] " FORMAT ", ", (RID), ##__VA_ARGS__);        \
+        }                                                                                                           \
+    }                                                                                                               \
+    while(0)
+
+#if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
+    #define UDS_LOG_RDATA_WITH_RID_QID_DNSSEC(CATEGORY, LEVEL, RID, QID, RR_PTR, FORMAT, ...)               \
+        do                                                                                                  \
+        {                                                                                                   \
+            const dnssec_result_t _dnssec_result = resource_record_get_validation_result(RR_PTR);           \
+            if (_dnssec_result == dnssec_indeterminate)                                                     \
+            {                                                                                               \
+                UDS_LOG_RDATA_WITH_RID_QID(CATEGORY, LEVEL, (RID), (QID), RR_PTR, FORMAT, ##__VA_ARGS__);   \
+            }                                                                                               \
+            else                                                                                            \
+            {                                                                                               \
+                UDS_LOG_RDATA_WITH_RID_QID(CATEGORY, LEVEL, (RID), (QID), RR_PTR,                           \
+                    FORMAT ", dnssec: " PUB_DNSSEC_RESULT, ##__VA_ARGS__, _dnssec_result);                  \
+            }                                                                                               \
+        }                                                                                                   \
+        while(0)
+#else
+    #define UDS_LOG_RDATA_WITH_RID_QID_DNSSEC UDS_LOG_RDATA_WITH_RID_QID
+#endif
+
+#define UDS_LOG_ANSWER_EVENT_WITH_FORMAT(CATEGORY, LEVEL, RID, QID, RR_PTR, EXPIRED, FORMAT, ...)       \
+    UDS_LOG_RDATA_WITH_RID_QID_DNSSEC(CATEGORY, LEVEL, RID, QID, RR_PTR, EXPIRED, FORMAT, ##__VA_ARGS__)
+
+#define UDS_LOG_ANSWER_EVENT(CATEGORY, LEVEL, REQUEST_PTR, Q_PTR, RR_PTR, EXPIRED, REQUEST_DESP, QC_RESULT)         \
+    do {                                                                                                            \
+        const mDNSu32 __ifIndex = mDNSPlatformInterfaceIndexfromInterfaceID(m, (RR_PTR)->InterfaceID, mDNSfalse);   \
+        const mDNSu32 __nameHash = mDNS_DomainNameFNV1aHash(&question->qname);                                      \
+        if (requestShouldLogName(REQUEST_PTR))                                                                      \
+        {                                                                                                           \
+            UDS_LOG_ANSWER_EVENT_WITH_FORMAT(CATEGORY, LEVEL,                                                       \
+                (REQUEST_PTR)->request_id, mDNSVal16((Q_PTR)->TargetQID), RR_PTR,                                   \
+                REQUEST_DESP " -- event: " PUB_ADD_RMV ", expired: " PUB_BOOL ", ifindex: %d, "                     \
+                "name: " PRI_DM_NAME " (%x)", ADD_RMV_U_PARAM(QC_RESULT), BOOL_PARAM(EXPIRED), __ifIndex,           \
+                DM_NAME_PARAM(&(Q_PTR)->qname), __nameHash);                                                        \
+        }                                                                                                           \
+        else                                                                                                        \
+        {                                                                                                           \
+            UDS_LOG_ANSWER_EVENT_WITH_FORMAT(CATEGORY, LEVEL,                                                       \
+                (REQUEST_PTR)->request_id, mDNSVal16((Q_PTR)->TargetQID), RR_PTR,                                   \
+                REQUEST_DESP " -- event: " PUB_ADD_RMV ", expired: " PUB_BOOL ", ifindex: %d, name hash: %x",       \
+                ADD_RMV_U_PARAM(QC_RESULT), BOOL_PARAM(EXPIRED), __ifIndex, __nameHash);                            \
+        }                                                                                                           \
+    } while (0)
+
 // ***************************************************************************
 // MARK: - General Utility Functions
 
@@ -406,6 +549,59 @@
 }
 #endif
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
+mDNSlocal dispatch_queue_t _get_trust_results_dispatch_queue(void)
+{
+    static dispatch_once_t  once    = 0;
+    static dispatch_queue_t queue   = NULL;
+
+    dispatch_once(&once, ^{
+        dispatch_queue_attr_t const attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0);
+        queue = dispatch_queue_create("com.apple.mDNSResponder.trust_results-queue", attr);
+    });
+    return queue;
+}
+
+mDNSlocal mDNSBool _prepare_trusts_for_request(request_state * const request)
+{
+    if (request->trusts == NULL)
+    {
+        request->trusts = CFArrayCreateMutable(kCFAllocatorDefault, 0, &mdns_cfarray_callbacks);
+        if (!request->trusts)
+        {
+            return mDNSfalse;
+        }
+    }
+    return mDNStrue;
+}
+
+#define mdns_trusts_forget_with_invalidation(PTR) \
+    do                                            \
+    {                                             \
+        if (*(PTR))                               \
+        {                                         \
+            (void)mdns_cfarray_enumerate(*(PTR),  \
+            ^ bool (mdns_trust_t trust)           \
+            {                                     \
+                mdns_trust_invalidate(trust);     \
+                return true;                      \
+            });                                   \
+            mdns_cf_forget((PTR));                \
+        }                                         \
+    } while(0)
+#endif
+
+mDNSlocal void resolve_result_finalize(request_resolve *resolve);
+#define resolve_result_forget(PTR)              \
+    do                                          \
+    {                                           \
+        if (*(PTR))                             \
+        {                                       \
+            resolve_result_finalize(*(PTR));    \
+            *(PTR) = NULL;                      \
+        }                                       \
+    } while(0)
+
 mDNSlocal void request_state_forget(request_state **const ptr)
 {
     request_state *req = *ptr;
@@ -413,7 +609,9 @@
 
     freeL("request_enumeration/request_state_forget", req->enumeration);
     freeL("request_servicereg/request_state_forget", req->servicereg);
-    freeL("request_resolve/request_state_forget", req->resolve);
+
+    resolve_result_forget(&req->resolve);
+
     freeL("QueryRecordClientRequest/request_state_forget", req->queryrecord);
     freeL("request_browse/request_state_forget", req->browse);
     freeL("request_port_mapping/request_state_forget", req->pm);
@@ -422,7 +620,7 @@
     mdns_forget(&req->peer_token);
 #endif
 #if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
-    mdns_trust_forget(&req->trust);
+    mdns_trusts_forget_with_invalidation(&req->trusts);
 #endif
 #if MDNSRESPONDER_SUPPORTS(APPLE, SIGNED_RESULTS)
     mdns_forget(&req->signed_obj);
@@ -742,26 +940,29 @@
     put_string(domstr, &data);
 }
 
-// get IPC_TLV_TYPE_SERVICE_ATTR_TIMESTAMP value
-// if tlv type IPC_TLV_TYPE_SERVICE_ATTR_TIMESTAMP is present and found is not NULL, *found will be set to mDNStrue,
-// otherwise it will be set to mDNSfalse. The caller should check *found before using the returned value. The timestamp
-// is a number of seconds in the past, and is unsigned.
-mDNSlocal mDNSu32 get_service_attr_timestamp_value(const request_state *const request, mDNSBool *const outFound)
+// get IPC_TLV_TYPE_SERVICE_ATTR_TIMESTAMP & IPC_TLV_TYPE_SERVICE_ATTR_HOST_KEY_HASH values
+// The timestamp is a number of seconds in the past, and is unsigned.
+mDNSlocal mDNSBool get_service_attr_tsr_params(const request_state *const request, mDNSu32 *const outTimestamp,
+    mDNSu32 *const outHostkeyHash)
 {
-    mDNSu32 timestamp = 0;
-    if (request->msgptr && (request->hdr.ipc_flags & IPC_FLAGS_TRAILING_TLVS))
+    if (request->msgptr && (request->hdr.ipc_flags & IPC_FLAGS_TRAILING_TLVS) &&
+        outTimestamp && outHostkeyHash)
     {
         mDNSs32 error;
         const mDNSu8 *const start = (const mDNSu8 *)request->msgptr;
         const mDNSu8 *const end   = (const mDNSu8 *)request->msgend;
-        timestamp = (mDNSu32)get_tlv_uint32(start, end, IPC_TLV_TYPE_SERVICE_ATTR_TIMESTAMP, &error);
-        if (outFound)
-        {
-            *outFound = error ? mDNSfalse : mDNStrue;
-        }
-        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEBUG, "found[" PUB_S "] timestamp %u", error ? "no" : "yes", timestamp);
+        mDNSu32 value = (mDNSu32)get_tlv_uint32(start, end, IPC_TLV_TYPE_SERVICE_ATTR_TIMESTAMP, &error);
+        *outTimestamp = value;
+        if (error) return mDNSfalse;
+
+        value = (mDNSu32)get_tlv_uint32(start, end, IPC_TLV_TYPE_SERVICE_ATTR_HOST_KEY_HASH, &error);
+        *outHostkeyHash = value;
+        if (error) return mDNSfalse;
+        LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEBUG,
+            "get_service_attr_tsr_params timestamp %u hostkeyHash %u", *outTimestamp, *outHostkeyHash);
+        return mDNStrue;
     }
-    return timestamp;
+    return mDNSfalse;
 }
 
 // Returns a resource record (allocated w/ malloc) containing the data found in an IPC message
@@ -981,20 +1182,6 @@
 }
 #endif  // MDNSRESPONDER_SUPPORTS(APPLE, D2D)
 
-#if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
-mDNSlocal dispatch_queue_t _get_trust_results_dispatch_queue(void)
-{
-    static dispatch_once_t  once    = 0;
-    static dispatch_queue_t queue   = NULL;
-
-    dispatch_once(&once, ^{
-        dispatch_queue_attr_t const attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0);
-        queue = dispatch_queue_create("com.apple.mDNSResponder.trust_results-queue", attr);
-    });
-    return queue;
-}
-#endif
-
 // ***************************************************************************
 // MARK: - DNSServiceRegister
 
@@ -1141,7 +1328,7 @@
             DomainNameLength(srs->RR_SRV.resrec.name));
 
         LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
-            "[R%u] DNSServiceRegister(" PRI_DM_NAME "(%x), %u) %s",
+            "[R%u] DNSServiceRegister(" PRI_DM_NAME " (%x), %u) %s",
             request_id, DM_NAME_PARAM(srs->RR_SRV.resrec.name), srv_name_hash,
             mDNSVal16(srs->RR_SRV.resrec.rdata->u.srv.port), result_description);
     }
@@ -1243,14 +1430,7 @@
     {
         if (result == mStatus_NoError)
         {
-            if (rr->resrec.rrtype == kDNSType_TSR)    // TSR record does not have parent struct
-            {
-                LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT, "regrecord_callback: successful registration of record " PRI_S, ARDisplayString(m, rr));
-            }
-            else
-            {
-                LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT, "Error: regrecord_callback: successful registration of orphaned record " PRI_S, ARDisplayString(m, rr));
-            }
+            LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT, "Error: regrecord_callback: successful registration of orphaned record " PRI_S, ARDisplayString(m, rr));
         }
         else
         {
@@ -1373,6 +1553,49 @@
 #endif  // LOCAL_PEEREPID
 }
 
+mDNSlocal mDNSu32 request_state_get_duration(const request_state *const request)
+{
+    return (mDNSu32)(mDNSPlatformContinuousTimeSeconds() - request->request_start_time_secs);
+}
+
+#define kRequestLogNamePeriodSecs (5 * MDNS_SECONDS_PER_MINUTE)
+
+mDNSlocal mDNSBool requestShouldLogName(request_state *const request)
+{
+    const mDNSs32 lastFullLogTimeSecs = request->last_full_log_time_secs;
+    const mDNSs32 nowTimeSecs = mDNSPlatformContinuousTimeSeconds();
+    const mDNSBool logName =
+        ((lastFullLogTimeSecs == 0) || ((nowTimeSecs - lastFullLogTimeSecs) >= kRequestLogNamePeriodSecs));;
+    if (logName)
+    {
+        request->last_full_log_time_secs = nowTimeSecs;
+    }
+    return logName;
+}
+
+#define kRequestLogFullRequestInfoPeriodSecs (5 * MDNS_SECONDS_PER_MINUTE)
+
+mDNSlocal mDNSBool requestShouldLogFullRequestInfo(request_state *const request)
+{
+    const mDNSs32 lastFullQInfoTimeSecs = request->request_start_time_secs;
+    const mDNSs32 nowTimeSecs = mDNSPlatformContinuousTimeSeconds();
+    mDNSBool logFullRInfo;
+    if (lastFullQInfoTimeSecs == 0)
+    {
+        request->request_start_time_secs = mDNSPlatformContinuousTimeSeconds();
+        logFullRInfo = mDNStrue;
+    }
+    else
+    {
+        logFullRInfo = ((nowTimeSecs - lastFullQInfoTimeSecs) >= kRequestLogFullRequestInfoPeriodSecs);
+    }
+    if (logFullRInfo)
+    {
+        request->last_full_log_time_secs = nowTimeSecs;
+    }
+    return logFullRInfo;
+}
+
 mDNSlocal void connection_termination(request_state *request)
 {
     // When terminating a shared connection, we need to scan the all_requests list
@@ -1403,9 +1626,9 @@
     {
         registered_record_entry *ptr = request->reg_recs;
         LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
-               "[R%d] DNSServiceRegisterRecord(0x%X, %d, " PRI_S ") STOP PID[%d](" PUB_S ")",
+               "[R%d] DNSServiceRegisterRecord(0x%X, %d, " PRI_S ") STOP PID[%d](" PUB_S ") -- duration: " PUB_TIME_DUR,
                request->request_id, request->flags, request->interfaceIndex, RRDisplayString(&mDNSStorage, &ptr->rr->resrec), request->process_id,
-               request->pid_name);
+               request->pid_name, request_state_get_duration(request));
         request->reg_recs = request->reg_recs->next;
         ptr->rr->RecordContext = NULL;
         if (ptr->external_advertise)
@@ -1416,6 +1639,13 @@
 #endif
         }
         LogMcastS(ptr->rr, request, reg_stop);
+#if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
+        if (ptr->powerlog_start_time != 0)
+        {
+            const mDNSBool usesAWDL = ClientRequestUsesAWDL(request->interfaceIndex, request->flags);
+            mdns_powerlog_register_record_stop(request->pid_name, ptr->powerlog_start_time, usesAWDL);
+        }
+#endif
         mDNS_Deregister(&mDNSStorage, ptr->rr);     // Will free ptr->rr for us
         freeL("registered_record_entry/connection_termination", ptr);
     }
@@ -1477,10 +1707,11 @@
     if (rr->resrec.rroriginalttl == 0)
         rr->resrec.rroriginalttl = DefaultTTLforRRType(rr->resrec.rrtype);
 
-    LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
-           "[R%d] DNSServiceRegisterRecord(0x%X, %d, " PRI_S ") START PID[%d](" PUB_S ")",
-           request->request_id, request->flags, request->interfaceIndex, RRDisplayString(&mDNSStorage, &rr->resrec), request->process_id,
-           request->pid_name);
+    const ResourceRecord *const record = &rr->resrec;
+    UDS_LOG_CLIENT_REQUEST(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
+        "DNSServiceRegisterRecord START",
+        record->name, request, mDNSfalse, "name: " PRI_DM_NAME ", type: " PUB_DNS_TYPE,
+        DM_NAME_PARAM_NONNULL(record->name), DNS_TYPE_PARAM(record->rrtype));
 
     err = mDNS_Register(&mDNSStorage, rr);
     if (err)
@@ -1493,6 +1724,13 @@
     }
     else
     {
+#if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
+        if ((rr->resrec.InterfaceID != mDNSInterface_LocalOnly) && IsLocalDomain(rr->resrec.name))
+        {
+            const mDNSBool usesAWDL = ClientRequestUsesAWDL(request->interfaceIndex, request->flags);
+            re->powerlog_start_time = mdns_powerlog_register_record_start(request->pid_name, usesAWDL);
+        }
+#endif
         LogMcastS(rr, request, reg_start);
         re->next = request->reg_recs;
         request->reg_recs = re;
@@ -1544,6 +1782,12 @@
             case mdns_trust_status_denied:
             case mdns_trust_status_pending:
             {
+                if (!_prepare_trusts_for_request(request))
+                {
+                    freeL("AuthRecord/_handle_regrecord_request_with_trust", rr);
+                    err = mStatus_NoMemoryErr;
+                    goto exit;
+                }
                 mdns_trust_t trust = mdns_trust_create(*token, service_ptr, flags);
                 if (!trust)
                 {
@@ -1580,7 +1824,8 @@
                         KQueueUnlock("_handle_regrecord_request_with_trust");
                     }
                 });
-                request->trust = trust;
+                CFArrayAppendValue(request->trusts, trust);
+                mdns_release(trust);
                 mdns_trust_activate(trust);
                 err = mStatus_NoError;
                 break;
@@ -1593,6 +1838,9 @@
             case mdns_trust_status_granted:
                 err = _handle_regrecord_request_start(request, rr);
                 break;
+
+            MDNS_COVERED_SWITCH_DEFAULT:
+                err = mStatus_UnknownErr;
         }
      }
 exit:
@@ -1601,27 +1849,30 @@
 #endif // TRUST_ENFORCEMENT
 
 // Add a TSR record when DNSServiceRegisterRecordWithAttribute is called with timestamp set correctly
-mDNSlocal mStatus regRecordAddTSRRecord(request_state *const request, AuthRecord *const rr, const mDNSs32 tsrTimestamp)
+mDNSlocal mStatus regRecordAddTSRRecord(request_state *const request, AuthRecord *const rr, const mDNSs32 tsrTimestamp, 
+    const mDNSu32 tsrHostkeyHash)
 {
     mStatus err = mStatus_NoError;
-    AuthRecord *ar;
     size_t rdcapacity = sizeof(RDataBody2);
-
-    ar = (AuthRecord *) callocL("AuthRecord/regRecordAddTSRRecord", sizeof(*ar) - sizeof(RDataBody) + rdcapacity);
+    AuthRecord *ar = (AuthRecord *) callocL("AuthRecord/regRecordAddTSRRecord", sizeof(*ar) - sizeof(RDataBody) + rdcapacity);
     if (!ar)
     {
         FatalError("ERROR: calloc");
     }
-    mDNS_SetupResourceRecord(ar, mDNSNULL, rr->resrec.InterfaceID, kDNSType_TSR, kHostNameTTL, kDNSRecordTypeUnique, AuthRecordAny, mDNSNULL, mDNSNULL);
+    mDNS_SetupResourceRecord(ar, mDNSNULL, rr->resrec.InterfaceID, kDNSType_OPT, kHostNameTTL, kDNSRecordTypeUnique, AuthRecordAny, mDNSNULL, mDNSNULL);
     AssignDomainName(&ar->namestorage, rr->resrec.name);
-    ar->resrec.rdlength = (mDNSu16)sizeof(tsrTimestamp);
-    ar->resrec.rdata->MaxRDLength = (mDNSu16)rdcapacity;
-    // tsr timestamp in memory is time of receipt
-    ar->resrec.rdata->u.tsr_value = tsrTimestamp;
-    ar->resrec.namehash = DomainNameHashValue(ar->resrec.name);
-    ar->RecordCallback = regrecord_callback;
+    ar->resrec.rrclass          = NormalMaxDNSMessageData;
+    ar->resrec.namehash         = rr->resrec.namehash;
+    ar->resrec.rdlength         = DNSOpt_TSRData_Space;
+    ar->resrec.rdestimate       = DNSOpt_TSRData_Space;
+    rdataOPT * const rdata = &ar->resrec.rdata->u.opt[0];
+    rdata->opt                  = kDNSOpt_TSR;
+    rdata->optlen               = DNSOpt_TSRData_Space - 4;
+    rdata->u.tsr.timeStamp      = tsrTimestamp;
+    rdata->u.tsr.hostkeyHash    = tsrHostkeyHash;
+    rdata->u.tsr.recIndex       = 0;
+    ar->RecordCallback          = regrecord_callback;
     SetNewRData(&ar->resrec, mDNSNULL, 0);  // Sets ar->rdatahash for us
-
     ar->ForceMCast = ((request->flags & kDNSServiceFlagsForceMulticast) != 0);
     LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT, "[R%d] regRecordAddTSRRecord(0x%X, %d, " PRI_S ") START PID[%d](" PUB_S ")",
               request->request_id, request->flags, request->interfaceIndex, RRDisplayString(&mDNSStorage, &ar->resrec),
@@ -1642,43 +1893,30 @@
     return err;
 }
 
-mDNSlocal mStatus updateTSRRecord(const request_state *const request, AuthRecord *const tsr, const mDNSs32 tsrTimestamp)
+mDNSlocal mStatus updateTSRRecord(const request_state *const request, AuthRecord *const tsr, const mDNSs32 tsrTimestamp, 
+    const mDNSu32 tsrHostkeyHash)
 {
     mStatus err = mStatus_NoError;
-    const RDataBody2 *const rdb = (RDataBody2 *)tsr->resrec.rdata->u.data;
-    mDNSu32 unsignedTimestamp = (mDNSu32)tsrTimestamp;
+    RDataBody2 *const rdb = (RDataBody2 *)tsr->resrec.rdata->u.data;
     LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT, "TSR timestamp - name: " PRI_DM_NAME ", new: %d  old: %d",
               DM_NAME_PARAM(tsr->resrec.name), tsrTimestamp, rdb->tsr_value);
-    if (tsrTimestamp - rdb->tsr_value > 0)
+    if (tsrTimestamp - rdb->opt[0].u.tsr.timeStamp > 0)
     {
-        const mDNSu8 rdata[4] = { (unsignedTimestamp >> 24) & 0xFF, (unsignedTimestamp >> 16) & 0xFF,
-            (unsignedTimestamp >> 8) & 0xFF, unsignedTimestamp & 0xFF};
-        err = update_record(tsr, sizeof(rdata), rdata, kHostNameTTL, mDNSNULL, request->request_id);
+        mDNSu32 optlen = DNSOpt_TSRData_Space - 4;
+        mDNSu32 uTimestamp = (mDNSu32)tsrTimestamp;
+        const mDNSu8 rdataOpt[DNSOpt_TSRData_Space] = {
+            (kDNSOpt_TSR >> 8) & 0xFF,          kDNSOpt_TSR & 0xFF,
+            (optlen >> 8) & 0xFF,               optlen & 0xFF,
+            (uTimestamp >> 24) & 0xFF,          (uTimestamp >> 16) & 0xFF,
+            (uTimestamp >> 8) & 0xFF,           uTimestamp & 0xFF,
+            (tsrHostkeyHash >> 24) & 0xFF,      (tsrHostkeyHash >> 16) & 0xFF,
+            (tsrHostkeyHash >> 8) & 0xFF,       tsrHostkeyHash & 0xFF,
+            0,                                  0 };
+        err = update_record(tsr, sizeof(rdataOpt), rdataOpt, kHostNameTTL, mDNSNULL, request->request_id);
     }
     return err;
 }
 
-mDNSlocal mDNSBool validateTSRTimestamp(mDNSs32 *timestampContinuous, mDNSu32 tsrTimestamp, const AuthRecord *rr)
-{
-    if (tsrTimestamp > MaxTimeSinceReceived)
-    {
-        if (rr != NULL)
-        {
-            LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_ERROR, "tsrTimestamp[%u] out of range (%u) on TSR for " PRI_DM_NAME "",
-                      tsrTimestamp, MaxTimeSinceReceived, DM_NAME_PARAM(rr->resrec.name));
-        }
-        else
-        {
-            LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_ERROR,
-                      "tsrTimestamp[%u] out of range (%u) on TSR", tsrTimestamp, MaxTimeSinceReceived);
-        }
-
-        return mDNSfalse;
-    }
-    *timestampContinuous = mDNSPlatformContinuousTimeSeconds() - (mDNSs32)tsrTimestamp;
-    return mDNStrue;
-}
-
 mDNSlocal mDNSBool conflictWithAuthRecords(mDNS *const m, const AuthRecord *const rr)
 {
     const AuthRecord *rp = m->ResourceRecords;
@@ -1687,7 +1925,7 @@
     while (rp)
     {
         const uintptr_t s2 = rp->RRSet ? rp->RRSet : (uintptr_t)rp;
-        if (rp->resrec.rrtype != kDNSType_TSR && s1 != s2 &&
+        if (rp->resrec.rrtype != kDNSType_OPT && s1 != s2 &&
             SameResourceRecordNameClassInterface(rp, rr) &&
             !IdenticalSameNameRecord(&rp->resrec, &rr->resrec) &&
             (rr->resrec.RecordType & kDNSRecordTypeUniqueMask || rp->resrec.RecordType & kDNSRecordTypeUniqueMask))
@@ -1704,29 +1942,78 @@
     return mDNSfalse;
 }
 
+mDNSlocal mDNSBool conflictWithCacheRecordsOrFlush(mDNS *const m, const mDNSu32 namehash, const domainname *const name,
+    const mDNSs32 validatedTSRTimestamp, const mDNSu32 tsrHostkeyHash)
+{
+    // Check for a matching TSR in the record cache.
+    // If it is newer (eTSRCheckWin) then exit with true (conflict)
+    // Otherwise, always clear the matching record cache entries.
+    const CacheGroup *cg = CacheGroupForName(m, namehash, name);
+    if (cg)
+    {
+        CacheRecord *cacheTSR = mDNSGetTSRForCacheGroup(cg);
+        if (cacheTSR)
+        {
+            const TSROptData newTSR = {validatedTSRTimestamp, tsrHostkeyHash, 0};
+            eTSRCheckResult tsrResult = CheckTSRForResourceRecord(&newTSR, &cacheTSR->resrec);
+            if (tsrResult == eTSRCheckWin)
+            {
+                return mDNStrue; // Existing TSR in cache is newer
+            }
+        }
+        // Flush cache for name since a TSR is authoritive for all Interfaces
+        CacheRecord *cr;
+        for (cr = cg ? cg->members : mDNSNULL; cr; cr=cr->next)
+        {
+            mDNS_PurgeCacheResourceRecord(m, cr);
+            LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEBUG,
+                "conflictWithCacheRecordsOrFlush - new TSR, flushing interface %d " PRI_S,
+                (int)IIDPrintable(cr->resrec.InterfaceID), CRDisplayString(m, cr));
+        }
+    }
+    return mDNSfalse;
+}
 
 mDNSlocal mStatus handle_regrecord_request(request_state *request)
 {
     mStatus err = mStatus_BadParamErr;
     AuthRecord *rr;
-    mDNSBool foundTimestampTLV = mDNSfalse;
 
     if (request->terminate != connection_termination)
     { LogMsg("%3d: DNSServiceRegisterRecord(not a shared connection ref)", request->sd); return(err); }
 
     rr = read_rr_from_ipc_msg(request, 1, 1);
-    const mDNSu32 tsrTimestamp = get_service_attr_timestamp_value(request, &foundTimestampTLV);
-    AuthRecord *currentTSR = mDNSGetTSRRecord(&mDNSStorage, rr);
+    mDNSu32 tsrTimestamp = 0, tsrHostkeyHash;
+    const mDNSBool foundTSRParams = get_service_attr_tsr_params(request, &tsrTimestamp, &tsrHostkeyHash);
+    mDNSs32 timestampContinuous = 0;
+    AuthRecord *currentTSR = mDNSNULL;
     if (rr)
     {
+        if (foundTSRParams && !getValidContinousTSRTime(&timestampContinuous, tsrTimestamp))
+        {
+            LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_ERROR, "tsrTimestamp[%u] out of range (%u) on TSR for " PRI_DM_NAME "",
+                      tsrTimestamp, MaxTimeSinceReceived, DM_NAME_PARAM(rr->resrec.name));
+            return mStatus_BadParamErr;
+        }
+        currentTSR = mDNSGetTSRForAuthRecord(&mDNSStorage, rr);
         rr->RRSet = (uintptr_t)request->sd;
-        if ((currentTSR || foundTimestampTLV) && conflictWithAuthRecords(&mDNSStorage, rr))
+        if ((currentTSR || foundTSRParams) && conflictWithAuthRecords(&mDNSStorage, rr))
         {
             LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT, "handle_regrecord_request: Name conflict " PRI_S " (%p), InterfaceID %p",
                       ARDisplayString(&mDNSStorage, rr), rr, rr->resrec.InterfaceID);
             freeL("AuthRecord/handle_regrecord_request", rr);
             return mStatus_NameConflict;
         }
+        const mDNSs32 validatedTSRTimestamp = (mDNSs32)tsrTimestamp;
+        if (foundTSRParams &&
+            conflictWithCacheRecordsOrFlush(&mDNSStorage, rr->resrec.namehash, rr->resrec.name, validatedTSRTimestamp, tsrHostkeyHash))
+        {
+            LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
+                "handle_regrecord_request: TSR Stale Data, record cache is newer " PRI_DM_NAME " InterfaceID %p",
+                DM_NAME_PARAM(rr->resrec.name), rr->resrec.InterfaceID);
+            freeL("AuthRecord/handle_regrecord_request", rr);
+            return mStatus_StaleData;
+        }
 #if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
         if (os_feature_enabled(mDNSResponder, bonjour_privacy) &&
             IsLocalDomain(rr->resrec.name))
@@ -1741,29 +2028,21 @@
         err = _handle_regrecord_request_start(request, rr);
 #endif
     }
-    if (!err && foundTimestampTLV)
+    if (!err && foundTSRParams)
     {
-        mDNSs32 timestampContinuous;
-        if (!validateTSRTimestamp(&timestampContinuous, tsrTimestamp, rr))
+        if (currentTSR)
         {
-            err = mStatus_BadParamErr;
+            err = updateTSRRecord(request, currentTSR, timestampContinuous, tsrHostkeyHash);
         }
         else
         {
-            if (currentTSR)
-            {
-                err = updateTSRRecord(request, currentTSR, timestampContinuous);
-            }
-            else
-            {
-                err = regRecordAddTSRRecord(request, rr, timestampContinuous);
-            }
+            err = regRecordAddTSRRecord(request, rr, timestampContinuous, tsrHostkeyHash);
         }
         if (!err)
         {
             LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
-                      "handle_regrecord_request: TSR record added with tsrTimestamp %d",
-                      timestampContinuous);
+                      "handle_regrecord_request: TSR record added with timestampContinuous %d tsrTimestamp %d tsrHostkeyHash %x",
+                      timestampContinuous, tsrTimestamp, tsrHostkeyHash);
         }
         else
         {
@@ -1808,10 +2087,17 @@
         service_instance *p = servicereg->instances;
         servicereg->instances = servicereg->instances->next;
         // only safe to free memory if registration is not valid, i.e. deregister fails (which invalidates p)
-        LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT, "[R%d] DNSServiceRegister(" PRI_DM_NAME "(%x), %u) STOP PID[%d](" PUB_S ")",
+        LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT, "[R%d] DNSServiceRegister(" PRI_DM_NAME " (%x), %u) STOP PID[%d](" PUB_S ") -- duration: " PUB_TIME_DUR,
                request->request_id, DM_NAME_PARAM(p->srs.RR_SRV.resrec.name),
                mDNS_NonCryptoHash(mDNSNonCryptoHash_FNV1a, p->srs.RR_SRV.resrec.name->c, DomainNameLength(p->srs.RR_SRV.resrec.name)),
-               mDNSVal16(p->srs.RR_SRV.resrec.rdata->u.srv.port), request->process_id, request->pid_name);
+               mDNSVal16(p->srs.RR_SRV.resrec.rdata->u.srv.port), request->process_id, request->pid_name,
+               request_state_get_duration(request));
+
+        const ResourceRecord *const srv_rr = &p->srs.RR_SRV.resrec;
+        UDS_LOG_CLIENT_REQUEST(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT, "DNSServiceRegister STOP",
+            SkipLeadingLabels(srv_rr->name, 1), request, mDNStrue, "SRV name: " PRI_DM_NAME " (%x), port: %u",
+            DM_NAME_PARAM(srv_rr->name), mDNS_DomainNameFNV1aHash(srv_rr->name), mDNSVal16(srv_rr->rdata->u.srv.port));
+
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, D2D)
         external_stop_advertising_helper(p);
@@ -1829,6 +2115,14 @@
             unlink_and_free_service_instance(p);
             // Don't touch service_instance *p after this -- it's likely to have been freed already
         }
+    #if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
+        if (request->powerlog_start_time != 0)
+        {
+            const mDNSBool usesAWDL = ClientRequestUsesAWDL(request->interfaceIndex, request->flags);
+            mdns_powerlog_service_register_stop(request->pid_name, request->powerlog_start_time, usesAWDL);
+            request->powerlog_start_time = 0;
+        }
+    #endif
     }
     if (servicereg->txtdata)
     {
@@ -2023,19 +2317,22 @@
     return result;
 }
 
-mDNSlocal mStatus handle_tsr_update_request(const request_state *const request, const AuthRecord *const rr, const mDNSu32 tsrTimestamp)
+mDNSlocal mStatus handle_tsr_update_request(const request_state *const request, const AuthRecord *const rr, 
+    const mDNSu32 tsrTimestamp, const mDNSu32 tsrHostkeyHash)
 {
     mStatus result = mStatus_NoError;
-    AuthRecord *currentTSR = mDNSGetTSRRecord(&mDNSStorage, rr);
+    AuthRecord *currentTSR = mDNSGetTSRForAuthRecord(&mDNSStorage, rr);
     mDNSs32 timestampContinuous;
-    if (!validateTSRTimestamp(&timestampContinuous, tsrTimestamp, rr))
+    if (!getValidContinousTSRTime(&timestampContinuous, tsrTimestamp))
     {
+        LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_ERROR, "tsrTimestamp[%u] out of range (%u) on TSR for " PRI_DM_NAME "",
+                  tsrTimestamp, MaxTimeSinceReceived, DM_NAME_PARAM(rr->resrec.name));
         result = mStatus_BadParamErr;
         goto end;
     }
     if (currentTSR)
     {
-        result = updateTSRRecord(request, currentTSR, timestampContinuous);
+        result = updateTSRRecord(request, currentTSR, timestampContinuous, tsrHostkeyHash);
     }
     else
     {
@@ -2054,7 +2351,6 @@
     mStatus result = mStatus_BadReferenceErr;
     service_instance *i;
     AuthRecord *rr = NULL;
-    mDNSBool foundTimestampTLV = mDNSfalse;
 
     // get the message data
     DNSServiceFlags flags = get_flags (&request->msgptr, request->msgend);  // flags unused
@@ -2070,10 +2366,14 @@
         return(mStatus_BadParamErr);
     }
 
-    const mDNSu32 tsrTimestamp = get_service_attr_timestamp_value(request, &foundTimestampTLV);
-    LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
-            "[R%d] DNSServiceUpdateRecord foundTimestampTLV[%s], tsrTimestamp[%u]", request->request_id,
-            foundTimestampTLV ? "true" : "false", tsrTimestamp);
+    mDNSu32 tsrTimestamp, tsrHostkeyHash;
+    const mDNSBool foundTSRParams = get_service_attr_tsr_params(request, &tsrTimestamp, &tsrHostkeyHash);
+    if (foundTSRParams)
+    {
+        LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
+            "[R%u] DNSServiceUpdateRecord foundTSRParams tsrTimestamp[%u] hostkeyHash[%x]",
+            request->request_id, tsrTimestamp, tsrHostkeyHash);
+    }
 
     // If this is a shared connection, check if the operation actually applies to a subordinate request_state object
     if (request->terminate == connection_termination) request = LocateSubordinateRequest(request);
@@ -2085,19 +2385,19 @@
         {
             if (reptr->key == hdr->reg_index)
             {
-                if (foundTimestampTLV)
+                if (foundTSRParams)
                 {
-                    result = handle_tsr_update_request(request, reptr->rr, tsrTimestamp);
+                    result = handle_tsr_update_request(request, reptr->rr, tsrTimestamp, tsrHostkeyHash);
                 }
                 else
                 {
                     result = update_record(reptr->rr, rdlen, rdata, ttl, &reptr->external_advertise, request->request_id);
                 }
                 LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
-                       "[R%d] DNSServiceUpdateRecord(" PRI_DM_NAME ", " PUB_S ") PID[%d](" PUB_S ")",
-                       request->request_id, DM_NAME_PARAM(reptr->rr->resrec.name),
-                       reptr->rr ? foundTimestampTLV ? "TSR" : DNSTypeName(reptr->rr->resrec.rrtype) : "<NONE>",
-                       request->process_id, request->pid_name);
+                    "[R%u] DNSServiceUpdateRecord(" PRI_DM_NAME ", " PUB_S PUB_S ") PID[%d](" PUB_S ")",
+                    request->request_id, DM_NAME_PARAM(reptr->rr->resrec.name),
+                    reptr->rr ? DNSTypeName(reptr->rr->resrec.rrtype) : "<NONE>", foundTSRParams ? " & TSR" : "",
+                    request->process_id, request->pid_name);
                 goto end;
             }
         }
@@ -2122,7 +2422,7 @@
     }
 
     // update the saved off TXT data for the service
-    if (!foundTimestampTLV && hdr->reg_index == TXT_RECORD_INDEX)
+    if (!foundTSRParams && hdr->reg_index == TXT_RECORD_INDEX)
     {
         if (servicereg->txtdata)
         { freeL("service_info txtdata", servicereg->txtdata); servicereg->txtdata = NULL; }
@@ -2147,9 +2447,9 @@
         }
 
         if (!rr) { result = mStatus_BadReferenceErr; goto end; }
-        if (foundTimestampTLV)
+        if (foundTSRParams)
         {
-            result = handle_tsr_update_request(request, rr, tsrTimestamp);
+            result = handle_tsr_update_request(request, rr, tsrTimestamp, tsrHostkeyHash);
             goto end;
         }
         else
@@ -2170,7 +2470,7 @@
             (srvName ? mDNS_NonCryptoHash(mDNSNonCryptoHash_FNV1a, srvName->c, DomainNameLength(srvName)) : 0);
         const uint16_t rrType = (rr ? rr->resrec.rrtype : 0);
         LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
-            "[R%u] DNSServiceUpdateRecord(" PRI_DM_NAME "(%x), " PUB_DNS_TYPE ") UPDATE PID[%d](%s)",
+            "[R%u] DNSServiceUpdateRecord(" PRI_DM_NAME " (%x), " PUB_DNS_TYPE ") UPDATE PID[%d](%s)",
             request->request_id, DM_NAME_PARAM(srvName), nameHash, DNS_TYPE_PARAM(rrType),
             request->process_id, request->pid_name);
     }
@@ -2199,6 +2499,13 @@
         e->external_advertise = mDNSfalse;
     }
     LogMcastS(e->rr, request, reg_stop);
+#if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
+    if (e->powerlog_start_time != 0)
+    {
+        const mDNSBool usesAWDL = ClientRequestUsesAWDL(request->interfaceIndex, request->flags);
+        mdns_powerlog_register_record_stop(request->pid_name, e->powerlog_start_time, usesAWDL);
+    }
+#endif
     err = mDNS_Deregister(&mDNSStorage, e->rr);     // Will free e->rr for us; we're responsible for freeing e
     if (err)
     {
@@ -2348,14 +2655,14 @@
     return(NumSubTypes);
 }
 
-mDNSlocal AuthRecord *AllocateSubTypes(mDNSu32 NumSubTypes, char *p)
+mDNSlocal mStatus AllocateSubTypes(mDNSu32 NumSubTypes, char *p, AuthRecord **subtypes)
 {
     AuthRecord *st = mDNSNULL;
     if (NumSubTypes)
     {
         mDNSu32 i;
         st = (AuthRecord *) callocL("ServiceSubTypes", NumSubTypes * sizeof(AuthRecord));
-        if (!st) return(mDNSNULL);
+        if (!st) return(mStatus_NoMemoryErr);
         for (i = 0; i < NumSubTypes; i++)
         {
             mDNS_SetupResourceRecord(&st[i], mDNSNULL, mDNSInterface_Any, kDNSQType_ANY, kStandardTTL, 0, AuthRecordAny, mDNSNULL, mDNSNULL);
@@ -2364,11 +2671,12 @@
             if (!MakeDomainNameFromDNSNameString(&st[i].namestorage, p))
             {
                 freeL("ServiceSubTypes", st);
-                return(mDNSNULL);
+                return(mStatus_BadParamErr);
             }
         }
     }
-    return(st);
+    *subtypes = st;
+    return(mStatus_NoError);
 }
 
 mDNSlocal mStatus register_service_instance(request_state *const request, const domainname *const domain)
@@ -2379,16 +2687,32 @@
     const mDNSBool DomainIsLocal = SameDomainName(domain, &localdomain);
     mStatus result;
     mDNSInterfaceID interfaceID = servicereg->InterfaceID;
-    mDNSBool foundTimestampTLV = mDNSfalse;
-    const mDNSu32 tsrTimestamp = get_service_attr_timestamp_value(request, &foundTimestampTLV);
+    mDNSu32 tsrTimestamp, tsrHostkeyHash;
+    const mDNSBool foundTSRParams = get_service_attr_tsr_params(request, &tsrTimestamp, &tsrHostkeyHash);
     mDNSs32 timestampContinuous = 0;
 
-    if (foundTimestampTLV)
+    if (foundTSRParams)
     {
-        if (!validateTSRTimestamp(&timestampContinuous, tsrTimestamp, NULL))
+        mDNSs32 validatedTSRTimestamp;
+        mDNSu32 namehash;
+        domainname full_hostname;
+
+        if(!getValidContinousTSRTime(&timestampContinuous, tsrTimestamp))
         {
+            LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_ERROR,
+                "tsrTimestamp[%u] out of range (%u) on TSR", tsrTimestamp, MaxTimeSinceReceived);
             return mStatus_BadParamErr;
         }
+        validatedTSRTimestamp = (mDNSs32)tsrTimestamp;
+        ConstructServiceName(&full_hostname, &servicereg->name, &servicereg->type, domain);
+        namehash = DomainNameHashValue(&full_hostname);
+        if (conflictWithCacheRecordsOrFlush(&mDNSStorage, namehash, &full_hostname, validatedTSRTimestamp, tsrHostkeyHash))
+        {
+            LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
+                "register_service_instance: TSR Stale Data, record cache is newer " PRI_DM_NAME " InterfaceID %p",
+                DM_NAME_PARAM(&full_hostname), interfaceID);
+            return mStatus_StaleData;
+        }
     }
 
     // If the client specified an interface, but no domain, then we honor the specified interface for the "local" (mDNS)
@@ -2419,13 +2743,20 @@
     instance->external_advertise            = mDNSfalse;
     AssignDomainName(&instance->domain, domain);
 
-    instance->subtypes = AllocateSubTypes(servicereg->num_subtypes, servicereg->type_as_string);
+    result = AllocateSubTypes(servicereg->num_subtypes, servicereg->type_as_string, &instance->subtypes);
 
-    if (servicereg->num_subtypes && !instance->subtypes)
+    if (result)
     {
         unlink_and_free_service_instance(instance);
         instance = NULL;
-        FatalError("ERROR: malloc");
+        if (result == mStatus_NoMemoryErr)
+        {
+            FatalError("ERROR: malloc");
+        }
+        else
+        {
+            return result;
+        }
     }
 
     result = mDNS_RegisterService(&mDNSStorage, &instance->srs,
@@ -2435,21 +2766,35 @@
                                   mDNSNULL, servicereg->txtdata, servicereg->txtlen,
                                   instance->subtypes, servicereg->num_subtypes,
                                   interfaceID, regservice_callback, instance, request->flags);
-    if (!result && foundTimestampTLV)
+#if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
+    if (!result && (request->interfaceIndex != kDNSServiceInterfaceIndexLocalOnly) && DomainIsLocal)
     {
-        AuthRecord *currentTSR = mDNSGetTSRRecord(&mDNSStorage, &instance->srs.RR_SRV);
+        const mDNSBool usesAWDL = ClientRequestUsesAWDL(request->interfaceIndex, request->flags);
+        request->powerlog_start_time = mdns_powerlog_service_register_start(request->pid_name, usesAWDL);
+    }
+#endif
+    if (!result && foundTSRParams)
+    {
+        AuthRecord *currentTSR = mDNSGetTSRForAuthRecord(&mDNSStorage, &instance->srs.RR_SRV);
 
         if (currentTSR)
         {
-            result = updateTSRRecord(request, currentTSR, timestampContinuous);
+            result = updateTSRRecord(request, currentTSR, timestampContinuous, tsrHostkeyHash);
         }
         else
         {
             // tsr timestamp in memory is absolute time of receipt
+            mDNSu32 optlen = DNSOpt_TSRData_Space - 4;
             mDNSu32 uTimestamp = (mDNSu32)timestampContinuous;
-            const mDNSu8 rdata[4] = { (uTimestamp >> 24) & 0xFF, (uTimestamp >> 16) & 0xFF,
-                                      (uTimestamp >> 8)  & 0xFF,  uTimestamp        & 0xFF };
-            result = add_record_to_service(request, instance, kDNSType_TSR, sizeof(rdata), rdata, kHostNameTTL);
+            const mDNSu8 rdataOpt[DNSOpt_TSRData_Space] = {
+                (kDNSOpt_TSR >> 8) & 0xFF,          kDNSOpt_TSR & 0xFF,
+                (optlen >> 8) & 0xFF,               optlen & 0xFF,
+                (uTimestamp >> 24) & 0xFF,          (uTimestamp >> 16) & 0xFF,
+                (uTimestamp >> 8) & 0xFF,           uTimestamp & 0xFF,
+                (tsrHostkeyHash >> 24) & 0xFF,      (tsrHostkeyHash >> 16) & 0xFF,
+                (tsrHostkeyHash >> 8) & 0xFF,       tsrHostkeyHash & 0xFF,
+                0,                                  0 };
+            result = add_record_to_service(request, instance, kDNSType_OPT, sizeof(rdataOpt), rdataOpt, kHostNameTTL);
         }
         if (!result)
         {
@@ -2465,15 +2810,13 @@
     if (!result)
     {
         *ptr = instance;        // Append this to the end of our servicereg->instances list
-        const mDNSu32 srv_name_hash = mDNS_NonCryptoHash(mDNSNonCryptoHash_FNV1a, instance->srs.RR_SRV.resrec.name->c,
-            DomainNameLength(instance->srs.RR_SRV.resrec.name));
-        const mDNSu32 ptr_name_hash = mDNS_NonCryptoHash(mDNSNonCryptoHash_FNV1a, instance->srs.RR_PTR.resrec.name->c,
-            DomainNameLength(instance->srs.RR_PTR.resrec.name));
 
+        // [R14] DNSServiceRegister result -- event: ADDED, SRV name: p001-ari0v37kf5o6d._dnssd-dp._tcp.local.(261cb2cf), port: 853, PTR name hash: 78e1c9c8
         LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
-            "[R%u] DNSServiceRegister(" PRI_DM_NAME "(%x), %u) ADDED "
-            "-- PTR name hash: %x", request->request_id, DM_NAME_PARAM(instance->srs.RR_SRV.resrec.name),
-            srv_name_hash, mDNSVal16(servicereg->port), ptr_name_hash);
+            "[R%u] DNSServiceRegister result -- event: ADDED, SRV name: " PRI_DM_NAME " (%x), port: %u, PTR name hash: %x",
+            request->request_id, DM_NAME_PARAM(instance->srs.RR_SRV.resrec.name),
+            mDNS_DomainNameFNV1aHash(instance->srs.RR_SRV.resrec.name), mDNSVal16(servicereg->port),
+            mDNS_DomainNameFNV1aHash(instance->srs.RR_PTR.resrec.name));
 
         LogMcastS(&instance->srs.RR_SRV, request, reg_start);
     }
@@ -2631,6 +2974,11 @@
             case mdns_trust_status_denied:
             case mdns_trust_status_pending:
             {
+                if (!_prepare_trusts_for_request(request))
+                {
+                    err = mStatus_NoMemoryErr;
+                    goto exit;
+                }
                 mdns_trust_t trust = mdns_trust_create(*token, servicereg->type_as_string, flags);
                 if (!trust)
                 {
@@ -2674,7 +3022,8 @@
                         KQueueUnlock("_register_service_instance_with_trust");
                     }
                 });
-                request->trust = trust;
+                CFArrayAppendValue(request->trusts, trust);
+                mdns_release(trust);
                 mdns_trust_activate(trust);
                 err = mStatus_NoError;
                 break;
@@ -2687,6 +3036,9 @@
             case mdns_trust_status_granted:
                 err = _handle_regservice_request_start(request, d);
                 break;
+
+            MDNS_COVERED_SWITCH_DEFAULT:
+                err = mStatus_UnknownErr;
         }
     }
 exit:
@@ -2841,18 +3193,11 @@
                    request->pid_name, count+1, srv.c, mDNSVal16(servicereg->port));
     }
 
-    // Construct the full service name.
-    mDNSu32 nameHash = mDNS_NonCryptoHash(mDNSNonCryptoHash_FNV1a, servicereg->name.c,
-        DomainLabelLength(&servicereg->name) + 1);
-    nameHash = mDNS_NonCryptoHashUpdateBytes(mDNSNonCryptoHash_FNV1a, nameHash, servicereg->type.c,
-        DomainNameLength(&servicereg->type) - 1);
-    nameHash = mDNS_NonCryptoHashUpdateBytes(mDNSNonCryptoHash_FNV1a, nameHash, d.c,
-        DomainNameLength(&d));
-
-    LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
-           "[R%d] DNSServiceRegister(%X, %d, \"" PRI_S "\", \"" PRI_S "\", \"" PRI_S "\", %x, \"" PRI_S "\", %u) START PID[%d](" PUB_S ")",
-           request->request_id, request->flags, interfaceIndex, name, servicereg->type_as_string, domain, nameHash, host,
-           mDNSVal16(servicereg->port), request->process_id, request->pid_name);
+    domainname ptr_name;
+    ConstructServiceName(&ptr_name, &servicereg->name, &servicereg->type, &d);
+    UDS_LOG_CLIENT_REQUEST(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT, "DNSServiceRegister START", &ptr_name, request,
+        mDNSfalse, "service type: " PRI_DM_NAME ", domain: " PRI_DM_NAME ", port: %u",
+        DM_NAME_PARAM(&servicereg->type), DM_NAME_PARAM(&d), mDNSVal16(servicereg->port));
 
     // We need to unconditionally set request->terminate, because even if we didn't successfully
     // start any registrations right now, subsequent configuration changes may cause successful
@@ -2898,6 +3243,7 @@
     DNSServiceFlags flags = AddRecord ? kDNSServiceFlagsAdd : 0;
     request_state *req = question->QuestionContext;
     reply_state *rep;
+    const mDNSBool isMDNSQuestion = mDNSOpaque16IsZero(question->TargetQID);
     (void)m; // Unused
 
     if (answer->rrtype != kDNSType_PTR)
@@ -2931,14 +3277,8 @@
     }
 
 validReply:
-
-    LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
-        "[R%d->Q%d] DNSServiceBrowse(" PRI_DM_NAME "(%x), " PUB_S ") RESULT " PUB_ADD_RMV_U " interface %d: " PRI_S,
-        req->request_id, mDNSVal16(question->TargetQID), DM_NAME_PARAM(&question->qname),
-        mDNS_NonCryptoHash(mDNSNonCryptoHash_FNV1a, question->qname.c, DomainNameLength(&question->qname)),
-        DNSTypeName(question->qtype), ADD_RMV_U_PARAM(AddRecord),
-        mDNSPlatformInterfaceIndexfromInterfaceID(m, answer->InterfaceID, mDNSfalse),
-        RRDisplayString(m, answer));
+    UDS_LOG_ANSWER_EVENT(isMDNSQuestion ? MDNS_LOG_CATEGORY_MDNS : MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
+        req, question, answer, mDNSfalse, "DNSServiceBrowse result", AddRecord);
 
     append_reply(req, rep);
 }
@@ -3052,7 +3392,7 @@
             mdns_cfarray_enumerate(definitions,
             ^ bool (const mdns_dns_service_definition_t definition)
             {
-                uint32_t ifIndex = mdns_dns_service_definition_get_interface_index(definition);
+                const uint32_t ifIndex = mdns_dns_service_definition_get_interface_index(definition);
                 mdns_address_t addr = mdns_dns_service_definition_get_first_address(definition);
                 if (!addr)
                 {
@@ -3110,6 +3450,13 @@
         b->next = browse->browsers;
         browse->browsers = b;
 
+#if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
+        if ((info->interfaceIndex != kDNSServiceInterfaceIndexLocalOnly) && SameDomainName(d, &localdomain))
+        {
+            const mDNSBool usesAWDL = ClientRequestUsesAWDL(info->interfaceIndex, info->flags);
+            info->powerlog_start_time = mdns_powerlog_browse_start(info->pid_name, usesAWDL);
+        }
+#endif
         LogMcastQ(&b->q, info, q_start);
 #if MDNSRESPONDER_SUPPORTS(APPLE, D2D)
         if (callExternalHelpers(browse->interface_id, &b->domain, info->flags))
@@ -3129,8 +3476,8 @@
     request_browse *const browse = info->browse;
     if (browse->default_domain)
     {
-        LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT, "[R%u] DNSServiceBrowse Cancel domain enumeration for WAB and mDNS "
-            "PID[%d](" PUB_S ")" , info->request_id, info->process_id, info->pid_name);
+        UDS_LOG_CLIENT_REQUEST(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
+            "DNSServiceBrowse Cancel domain enumeration for WAB and mDNS", mDNSNULL, info, mDNStrue, "");
         // Stop the domain enumeration queries to discover the WAB legacy browse domains
         uDNS_StopWABQueries(&mDNSStorage, UDNS_WAB_LBROWSE_QUERY);
 
@@ -3148,22 +3495,26 @@
         {
             domainname tmp;
             ConstructServiceName(&tmp, NULL, &browse->regtype, &ptr->domain);
-            LogInfo("browse_termination_callback: calling external_stop_browsing_for_service()");
             external_stop_browsing_for_service(ptr->q.InterfaceID, &tmp, kDNSType_PTR, ptr->q.flags, info->process_id);
         }
 #endif
 
-        LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
-               "[R%d] DNSServiceBrowse(%X, %d, \"" PRI_DM_NAME "\"(%x)) STOP PID[%d](" PUB_S ")",
-               info->request_id, info->flags, info->interfaceIndex, DM_NAME_PARAM(&ptr->q.qname),
-               mDNS_NonCryptoHash(mDNSNonCryptoHash_FNV1a, ptr->q.qname.c, DomainNameLength(&ptr->q.qname)),
-               info->process_id, info->pid_name);
+        UDS_LOG_CLIENT_REQUEST(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT, "DNSServiceBrowse STOP", &ptr->q.qname,
+            info, mDNStrue, "service name: " PRI_DM_NAME, DM_NAME_PARAM(&ptr->q.qname));
 
         browse->browsers = ptr->next;
         mDNS_StopBrowse(&mDNSStorage, &ptr->q);  // no need to error-check result
         LogMcastQ(&ptr->q, info, q_stop);
         freeL("browser_t/browse_termination_callback", ptr);
     }
+#if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
+    if (info->powerlog_start_time != 0)
+    {
+        const mDNSBool usesAWDL = ClientRequestUsesAWDL(info->interfaceIndex, info->flags);
+        mdns_powerlog_browse_stop(info->pid_name, info->powerlog_start_time, usesAWDL);
+        info->powerlog_start_time = 0;
+    }
+#endif
 }
 
 mDNSlocal void udsserver_automatic_browse_domain_changed(const DNameListElem *const d, const mDNSBool add)
@@ -3563,15 +3914,17 @@
     else RmvAutoBrowseDomain(0, &answer->rdata->u.name);
 
 #if MDNSRESPONDER_SUPPORTS(COMMON, LOCAL_DNS_RESOLVER_DISCOVERY)
-    // We also start the local DNS resolver discovery if the automatic browsing domain discovered is the Thread domain.
-    if (SameDomainName(&answer->rdata->u.name, THREAD_DOMAIN_NAME))
+    // We also start the local DNS resolver discovery if the automatic browsing domain discovered is a unicast domain
+    // where we can do discovery via Do53.
+    if (!IsRootDomain(Do53_UNICAST_DISCOVERY_DOMAIN) &&
+        SameDomainName(&answer->rdata->u.name, Do53_UNICAST_DISCOVERY_DOMAIN))
     {
         // AutomaticBrowseDomainChange() is called as a callback function where the mDNS_Lock is dropped, to start the
         // resolver discovery process, we need to grab the mDNS_Lock again.
         if (AddRecord == QC_add) {
-            resolver_discovery_add(THREAD_DOMAIN_NAME, mDNStrue);
+            resolver_discovery_add(Do53_UNICAST_DISCOVERY_DOMAIN, mDNStrue);
         } else {
-            resolver_discovery_remove(THREAD_DOMAIN_NAME, mDNStrue);
+            resolver_discovery_remove(Do53_UNICAST_DISCOVERY_DOMAIN, mDNStrue);
         }
     }
 #endif
@@ -3665,6 +4018,11 @@
             case mdns_trust_status_denied:
             case mdns_trust_status_pending:
             {
+                if (!_prepare_trusts_for_request(request))
+                {
+                    err = mStatus_NoMemoryErr;
+                    goto exit;
+                }
                 mdns_trust_t trust = mdns_trust_create(*token, typestr, flags);
                 if (!trust )
                 {
@@ -3710,7 +4068,8 @@
                         KQueueUnlock("_handle_browse_request_with_trust");
                     }
                 });
-                request->trust = trust;
+                CFArrayAppendValue(request->trusts, trust);
+                mdns_release(trust);
                 mdns_trust_activate(trust);
                 err = mStatus_NoError;
                 break;
@@ -3723,6 +4082,9 @@
             case mdns_trust_status_granted:
                 err = _handle_browse_request_start(request, domain);
                 break;
+
+            MDNS_COVERED_SWITCH_DEFAULT:
+                err = mStatus_UnknownErr;
         }
     }
 exit:
@@ -3801,9 +4163,9 @@
     browse->default_domain = !domain[0];
     browse->browsers = NULL;
 
-    LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT, "[R%d] DNSServiceBrowse(%X, %d, \"" PRI_DM_NAME "\", \"" PRI_S "\") START PID[%d](" PUB_S ")",
-           request->request_id, request->flags, interfaceIndex, DM_NAME_PARAM(&browse->regtype), domain,
-           request->process_id, request->pid_name);
+    UDS_LOG_CLIENT_REQUEST(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
+        "DNSServiceBrowse START", mDNSNULL, request, mDNSfalse, "service type: " PRI_DM_NAME ", domain: " PRI_S,
+        DM_NAME_PARAM(&browse->regtype), domain);
 
     if (browse->default_domain)
     {
@@ -3849,11 +4211,11 @@
 mDNSlocal void resolve_termination_callback(request_state *request)
 {
     request_resolve *const resolve = request->resolve;
-    LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
-       "[R%d] DNSServiceResolve(%X, %d, \"" PRI_DM_NAME "\"(%x)) STOP PID[%d](" PUB_S ")",
-       request->request_id, request->flags, request->interfaceIndex, DM_NAME_PARAM(&resolve->qtxt.qname),
-       mDNS_NonCryptoHash(mDNSNonCryptoHash_FNV1a, resolve->qtxt.qname.c, DomainNameLength(&resolve->qtxt.qname)),
-       request->process_id, request->pid_name);
+
+    UDS_LOG_CLIENT_REQUEST(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
+        "DNSServiceResolve STOP",
+        &resolve->qtxt.qname, request, mDNStrue, "SRV name: " PRI_DM_NAME, DM_NAME_PARAM_NONNULL(&resolve->qtxt.qname));
+
     mDNS_StopQuery(&mDNSStorage, &resolve->qtxt);
     mDNS_StopQuery(&mDNSStorage, &resolve->qsrv);
     LogMcastQ(&resolve->qsrv, request, q_stop);
@@ -3863,6 +4225,14 @@
         external_stop_resolving_service(resolve->qsrv.InterfaceID, &resolve->qsrv.qname, request->flags, request->process_id);
     }
 #endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
+    if (request->powerlog_start_time != 0)
+    {
+        const mDNSBool usesAWDL = ClientRequestUsesAWDL(request->interfaceIndex, request->flags);
+        mdns_powerlog_resolve_stop(request->pid_name, request->powerlog_start_time, usesAWDL);
+        request->powerlog_start_time = 0;
+    }
+#endif
 }
 
 typedef struct {
@@ -3888,62 +4258,200 @@
         else
         {
             request->terminate = resolve_termination_callback;
+        #if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
+            if ((request->interfaceIndex != kDNSServiceInterfaceIndexLocalOnly) && IsLocalDomain(&params->fqdn))
+            {
+                const mDNSBool usesAWDL = ClientRequestUsesAWDL(request->interfaceIndex, request->flags);
+                request->powerlog_start_time = mdns_powerlog_resolve_start(request->pid_name, usesAWDL);
+            }
+        #endif
             LogMcastQ(&resolve->qsrv, request, q_start);
-#if MDNSRESPONDER_SUPPORTS(APPLE, D2D)
+        #if MDNSRESPONDER_SUPPORTS(APPLE, D2D)
             if (callExternalHelpers(params->InterfaceID, &params->fqdn, request->flags))
             {
                 resolve->external_advertise    = mDNStrue;
                 LogInfo("handle_resolve_request: calling external_start_resolving_service()");
                 external_start_resolving_service(params->InterfaceID, &params->fqdn, request->flags, request->process_id);
             }
-#else
+        #else
             (void)params;
-#endif
+        #endif
         }
     }
     return err;
 }
 
+mDNSlocal void resolve_result_forget_srv(request_resolve *const resolve)
+{
+    mDNSPlatformMemForget(&resolve->srv_target_name);
+    resolve->srv_port = zeroIPPort;
+    resolve->srv_negative = mDNSfalse;
+}
+
+mDNSlocal void resolve_result_forget_txt(request_resolve *const resolve)
+{
+    mDNSPlatformMemForget(&resolve->txt_rdata);
+    resolve->txt_rdlength = 0;
+    resolve->txt_negative = mDNSfalse;
+}
+
+mDNSlocal void resolve_result_finalize(request_resolve *resolve)
+{
+    mDNSPlatformMemForget(&resolve->srv_target_name);
+    mDNSPlatformMemForget(&resolve->txt_rdata);
+    freeL("request_resolve/request_state_forget", resolve);
+}
+
+mDNSlocal mDNSBool resolve_result_is_complete(const request_resolve *const resolve)
+{
+    // Positive and negative answers are both considered "completed responses"
+    const mDNSBool got_srv = (resolve->srv_negative || resolve->srv_target_name);
+    const mDNSBool got_txt = (resolve->txt_negative || resolve->txt_rdata);
+    const mDNSBool response_completes = (got_srv && got_txt);
+    return response_completes;
+}
+
+mDNSlocal void resolve_result_save_answer(request_resolve *const resolve, const ResourceRecord *const answer,
+    const QC_result add_record)
+{
+    const mDNSu16 rrtype = answer->rrtype;
+
+    // If the record is being removed, in this case, only positive answer will be removed.
+    if (!add_record)
+    {
+        // Clear any positive rdata we held previously
+        if (rrtype == kDNSType_SRV)
+        {
+            resolve_result_forget_srv(resolve);
+        }
+        else
+        {
+            resolve_result_forget_txt(resolve);
+        }
+
+        // For resolve request, we do not deliver remove event to the client.
+        return;
+    }
+
+    // The answer is newly added.
+    const mDNSBool negative_answer = (answer->RecordType == kDNSRecordTypePacketNegative);
+    if (rrtype == kDNSType_SRV)
+    {
+        mDNSPlatformMemForget(&resolve->srv_target_name);
+        if (negative_answer)
+        {
+            resolve->srv_port = zeroIPPort;
+            resolve->srv_negative = mDNStrue;
+        }
+        else
+        {
+            // Copy the target name and port number from the rdata of the SRV record.
+            const domainname *const target_name = &answer->rdata->u.srv.target;
+            const mDNSu16 target_name_length = DomainNameLength(target_name);
+            mdns_require_return(target_name_length > 0);
+
+            resolve->srv_target_name = mDNSPlatformMemAllocateClear(target_name_length);
+            mdns_require_return(resolve->srv_target_name);
+
+            AssignDomainName(resolve->srv_target_name, target_name);
+            resolve->srv_port = answer->rdata->u.srv.port;
+            resolve->srv_negative = mDNSfalse;
+        }
+    }
+    else
+    {
+        mDNSPlatformMemForget(&resolve->txt_rdata);
+        if (negative_answer)
+        {
+            resolve->txt_rdlength = 0;
+            resolve->txt_negative = mDNStrue;
+        }
+        else
+        {
+            // Copy the rdata of TXT record directly.
+            const mDNSu8 *const txt_rdata = answer->rdata->u.data;
+            const mDNSu16 txt_rdlength = answer->rdlength;
+
+            // MAX(1, txt_rdlength) ensures that resolve->txt_rdata is non-null, when the TXT record rdata length is 0,
+            // thus allowing the 0-length TXT to be identified as a positive record.
+            // In theory, TXT record should contain "One or more <character-string>s." according to:
+            // [TXT RDATA format](https://datatracker.ietf.org/doc/html/rfc1035#section-3.3.14)
+            // Here we allow mDNSResponder to return TXT record with a 0 data length.
+            resolve->txt_rdata = mDNSPlatformMemAllocateClear(MAX(1, txt_rdlength));
+            mdns_require_return(resolve->txt_rdata);
+
+            mDNSPlatformMemCopy(resolve->txt_rdata, txt_rdata, txt_rdlength);
+            resolve->txt_rdlength = txt_rdlength;
+            resolve->txt_negative = mDNSfalse;
+        }
+    }
+}
+
 mDNSlocal void resolve_result_callback(mDNS *const m, DNSQuestion *question, const ResourceRecord *const answer, QC_result AddRecord)
 {
     size_t len = 0;
     char fullname[MAX_ESCAPED_DOMAIN_NAME], target[MAX_ESCAPED_DOMAIN_NAME] = "0";
     uint8_t *data;
     reply_state *rep;
-    const DNSServiceErrorType error =
-        (answer->RecordType == kDNSRecordTypePacketNegative) ? kDNSServiceErr_NoSuchRecord : kDNSServiceErr_NoError;
     (void)m; // Unused
 
     request_state *const req = question->QuestionContext;
-    const mDNSu32 name_hash = mDNS_NonCryptoHash(mDNSNonCryptoHash_FNV1a, question->qname.c,
-        DomainNameLength(&question->qname));
+    const mDNSu32 name_hash = mDNS_DomainNameFNV1aHash(&question->qname);
 
-    LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
-        "[R%u] DNSServiceResolve(" PRI_DM_NAME "(%x)) " PUB_ADD_RMV_U " interface %u: " PRI_S,
-        req->request_id, DM_NAME_PARAM(&question->qname), name_hash, ADD_RMV_U_PARAM(AddRecord),
-        mDNSPlatformInterfaceIndexfromInterfaceID(m, answer->InterfaceID, mDNSfalse), RRDisplayString(m, answer));
+    const mDNSBool isMDNSQuestion = mDNSOpaque16IsZero(question->TargetQID);
+    UDS_LOG_ANSWER_EVENT(isMDNSQuestion ? MDNS_LOG_CATEGORY_MDNS : MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
+        req, question, answer, mDNSfalse, "DNSServiceResolve result", AddRecord);
+
+    const mDNSu16 rrtype = answer->rrtype;
+    mdns_require_return((rrtype == kDNSType_SRV) || (rrtype == kDNSType_TXT));
 
     request_resolve *const resolve = req->resolve;
-    if (!AddRecord)
+    resolve_result_save_answer(resolve, answer, AddRecord);
+    if (!resolve_result_is_complete(resolve))
     {
-        if (resolve->srv == answer) resolve->srv = mDNSNULL;
-        if (resolve->txt == answer) resolve->txt = mDNSNULL;
+        // Wait until we have both SRV record and TXT record(either positive or negative).
         return;
     }
 
-    if (answer->rrtype == kDNSType_SRV) resolve->srv = answer;
-    if (answer->rrtype == kDNSType_TXT) resolve->txt = answer;
-
-    if (!resolve->txt || !resolve->srv) return;     // only deliver result to client if we have both answers
+    // 4 cases:
+    // SRV positive, TXT positive -> kDNSServiceErr_NoError
+    // SRV negative, TXT positive -> kDNSServiceErr_NoSuchRecord
+    // SRV positive, TXT negative -> kDNSServiceErr_NoError
+    // SRV negative, TXT negative -> kDNSServiceErr_NoSuchRecord
+    // The intuition here is that having positive SRV or not decides whether we are able to use the service.
+    // The TXT record only provides auxiliary information about the service.
+    const DNSServiceErrorType error = ((resolve->srv_negative) ? kDNSServiceErr_NoSuchRecord : kDNSServiceErr_NoError);
 
     ConvertDomainNameToCString(answer->name, fullname);
 
+    // Prepare the data to be returned to the client.
     mDNSu32 target_name_hash = 0;
-    if (answer->RecordType != kDNSRecordTypePacketNegative)
+    if (!resolve->srv_negative)
     {
-        ConvertDomainNameToCString(&resolve->srv->rdata->u.srv.target, target);
-        target_name_hash = mDNS_NonCryptoHash(mDNSNonCryptoHash_FNV1a, resolve->srv->rdata->u.srv.target.c,
-            DomainNameLength(&resolve->srv->rdata->u.srv.target));
+        const domainname *const srv_target = resolve->srv_target_name;
+        const mDNSu16 srv_target_len = DomainNameLength(srv_target);
+        ConvertDomainNameToCString(srv_target, target);
+        target_name_hash = mDNS_NonCryptoHash(mDNSNonCryptoHash_FNV1a, srv_target->c, srv_target_len);
+    }
+
+    // We need to return SRV target name, SRV port number and TXT rdata to the client.
+    // Set them to the empty data filled with 0 initially.
+    const mDNSu8 temp_empty_data[1] = {0};
+    const mDNSu8 *srv_target_data = temp_empty_data;
+    mDNSIPPort srv_port = {0};
+    const mDNSu8 *txt_rdata = temp_empty_data;
+    mDNSu16 txt_rdlength = 0;
+
+    // If SRV or TXT record is positive, set the pointer to the rdata we have copied before.
+    if (!resolve->srv_negative)
+    {
+        srv_target_data = resolve->srv_target_name->c;
+        srv_port = resolve->srv_port;
+    }
+    if (!resolve->txt_negative)
+    {
+        txt_rdata = resolve->txt_rdata;
+        txt_rdlength = resolve->txt_rdlength;
     }
 
     mDNSu32 interface_index = mDNSPlatformInterfaceIndexfromInterfaceID(m, answer->InterfaceID, mDNSfalse);
@@ -3954,7 +4462,7 @@
     len += strlen(fullname) + 1;
     len += strlen(target) + 1;
     len += 2 * sizeof(mDNSu16);  // port, txtLen
-    len += resolve->txt->rdlength;
+    len += txt_rdlength;
 #if MDNSRESPONDER_SUPPORTS(APPLE, SIGNED_RESULTS)
     mdns_signed_resolve_result_t signed_result = NULL;
     const uint8_t *signed_data = NULL;
@@ -3972,9 +4480,9 @@
         }
         else
         {
-            signed_result = mdns_signed_resolve_result_create(browseResult, resolve->srv->rdata->u.srv.target.c,
-                resolve->srv->rdata->u.srv.port.NotAnInteger, interface_index, resolve->txt->rdata->u.data,
-                resolve->txt->rdlength, &err);
+            // If the SRV record is negative, then we will sign rdata filled with zeros.
+            signed_result = mdns_signed_resolve_result_create(browseResult, srv_target_data, srv_port.NotAnInteger,
+                interface_index, txt_rdata, txt_rdlength, &err);
         }
         if (!signed_result || err != 0)
         {
@@ -3994,6 +4502,9 @@
             }
         }
     }
+#else
+    // To suppress the unused variable warning when `MDNSRESPONDER_SUPPORTS(APPLE, SIGNED_RESULTS)` is disabled.
+    (void)srv_target_data;
 #endif
 
     // allocate/init reply header
@@ -4007,10 +4518,10 @@
     // write reply data to message
     put_string(fullname, &data);
     put_string(target, &data);
-    *data++ =  resolve->srv->rdata->u.srv.port.b[0];
-    *data++ =  resolve->srv->rdata->u.srv.port.b[1];
-    put_uint16(resolve->txt->rdlength, &data);
-    put_rdata(resolve->txt->rdlength, resolve->txt->rdata->u.data, &data);
+    *data++ = srv_port.b[0];
+    *data++ = srv_port.b[1];
+    put_uint16(txt_rdlength, &data);
+    put_rdata(txt_rdlength, txt_rdata, &data);
 #if MDNSRESPONDER_SUPPORTS(APPLE, SIGNED_RESULTS)
     if (signed_data)
     {
@@ -4019,10 +4530,19 @@
     mdns_forget(&signed_result);
 #endif
 
-    LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
-        "[R%d->Q%d] DNSServiceResolve(" PRI_S "(%x)) RESULT   " PRI_S "(%x):%d",
-        req->request_id, mDNSVal16(question->TargetQID), fullname, name_hash, target, target_name_hash,
-        mDNSVal16(resolve->srv->rdata->u.srv.port));
+    if (error == kDNSServiceErr_NoError)
+    {
+        LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
+            "[R%d->Q%d] DNSServiceResolve(" PRI_S " (%x)) RESULT   " PRI_S " (%x):%d",
+            req->request_id, mDNSVal16(question->TargetQID), fullname, name_hash, target, target_name_hash,
+            mDNSVal16(srv_port));
+    }
+    else
+    {
+        LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
+            "[R%d->Q%d] DNSServiceResolve(" PRI_S " (%x)) NoSuchRecord",
+            req->request_id, mDNSVal16(question->TargetQID), fullname, name_hash);
+    }
     append_reply(req, rep);
 }
 
@@ -4081,6 +4601,11 @@
             case mdns_trust_status_denied:
             case mdns_trust_status_pending:
             {
+                if (!_prepare_trusts_for_request(request))
+                {
+                    err = mStatus_NoMemoryErr;
+                    goto exit;
+                }
                 mdns_trust_t trust = mdns_trust_create(*token, params->regtype, flags);
                 if (!trust )
                 {
@@ -4125,7 +4650,8 @@
                         KQueueUnlock("_handle_resolve_request_with_trust");
                     }
                 });
-                request->trust = trust;
+                CFArrayAppendValue(request->trusts, trust);
+                mdns_release(trust);
                 mdns_trust_activate(trust);
                 err = mStatus_NoError;
                 break;
@@ -4138,6 +4664,9 @@
             case mdns_trust_status_granted:
                 err = _handle_resolve_request_start(request, params);
                 break;
+
+            MDNS_COVERED_SWITCH_DEFAULT:
+                err = mStatus_UnknownErr;
         }
     }
 exit:
@@ -4257,12 +4786,8 @@
     if (!AuthorizedDomain(request, &fqdn, AutoBrowseDomains)) return(mStatus_NoError);
 #endif
 
-    // ask the questions
-    LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
-        "[R%d] DNSServiceResolve(%X, %d, \"" PRI_DM_NAME "\"(%x)) START PID[%d](" PUB_S ")",
-        request->request_id, flags, interfaceIndex, DM_NAME_PARAM(&resolve->qsrv.qname),
-        mDNS_NonCryptoHash(mDNSNonCryptoHash_FNV1a, resolve->qsrv.qname.c, DomainNameLength(&resolve->qsrv.qname)),
-        request->process_id, request->pid_name);
+    UDS_LOG_CLIENT_REQUEST(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT, "DNSServiceResolve START",
+        &resolve->qsrv.qname, request, mDNSfalse, "SRV name: " PRI_DM_NAME, DM_NAME_PARAM(&resolve->qsrv.qname));
 
     request->terminate = NULL;
 #if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
@@ -4356,24 +4881,23 @@
     ConvertDomainNameToCString(answer->name, name);
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, DNSSECv2)
-    dnssec_result_t dnssec_result = dnssec_indeterminate;
     if (dns_question_is_dnssec_requestor(question))
     {
         flags |= dns_service_flags_init_with_dnssec_result(question, answer);
-        dnssec_result = resource_record_get_validation_result(answer);
     }
 #endif
 
-    const mDNSBool localDomain = IsLocalDomain(&question->qname);
-
-    LogRedact(localDomain ? MDNS_LOG_CATEGORY_MDNS : MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
-       "[R%u->Q%u] DNSService" PUB_S "(" PRI_DM_NAME "(%x), " PUB_DNS_TYPE ") RESULT " PUB_ADD_RMV_U " interface %d: (" PUB_MORTALITY ", " PUB_DNSSEC_RESULT ")" PRI_S,
-       req->request_id, mDNSVal16(question->TargetQID), req->hdr.op == query_request ? "QueryRecord" : "GetAddrInfo",
-       DM_NAME_PARAM(&question->qname),
-       mDNS_NonCryptoHash(mDNSNonCryptoHash_FNV1a, question->qname.c, DomainNameLength(&question->qname)),
-       DNS_TYPE_PARAM(question->qtype), ADD_RMV_U_PARAM(AddRecord),
-       mDNSPlatformInterfaceIndexfromInterfaceID(m, answer->InterfaceID, mDNSfalse),
-       MORTALITY_PARAM(answer->mortality), DNSSEC_RESULT_PARAM(dnssec_result), RRDisplayString(m, answer));
+    const mDNSBool isMDNSQuestion = mDNSOpaque16IsZero(question->TargetQID);
+    if (req->hdr.op == query_request)
+    {
+        UDS_LOG_ANSWER_EVENT(isMDNSQuestion ? MDNS_LOG_CATEGORY_MDNS : MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
+            req, question, answer, expired, "DNSServiceQueryRecord result", AddRecord);
+    }
+    else
+    {
+        UDS_LOG_ANSWER_EVENT(isMDNSQuestion ? MDNS_LOG_CATEGORY_MDNS : MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
+            req, question, answer, expired, "DNSServiceGetAddrInfo result", AddRecord);
+    }
 
     // Call mDNSPlatformInterfaceIndexfromInterfaceID, but suppressNetworkChange (last argument). Otherwise, if the
     // InterfaceID is not valid, then it simulates a "NetworkChanged" which in turn makes questions
@@ -4526,19 +5050,27 @@
     const domainname *const qname = QueryRecordClientRequestGetQName(request->queryrecord);
     const mDNSBool localDomain = IsLocalDomain(qname);
 
-    LogRedact(localDomain ? MDNS_LOG_CATEGORY_MDNS : MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
-        "[R%u] DNSServiceQueryRecord(%X, %d, " PRI_DM_NAME "(%x), " PUB_S ") STOP PID[%d](" PUB_S ")",
-        request->request_id, request->flags, request->interfaceIndex,
-        DM_NAME_PARAM(qname), mDNS_NonCryptoHash(mDNSNonCryptoHash_FNV1a, qname->c, DomainNameLength(qname)),
-        DNSTypeName(QueryRecordClientRequestGetType(request->queryrecord)), request->process_id, request->pid_name);
+    const mDNSu16 qtype = QueryRecordClientRequestGetType(request->queryrecord);
+    UDS_LOG_CLIENT_REQUEST(localDomain ? MDNS_LOG_CATEGORY_MDNS : MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
+        "DNSServiceQueryRecord STOP", qname, request, mDNStrue, "qname: " PRI_DM_NAME ", qtype: " PUB_DNS_TYPE,
+        DM_NAME_PARAM_NONNULL(qname), DNS_TYPE_PARAM(qtype));
 
     QueryRecordClientRequestStop(request->queryrecord);
+#if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
+    if (request->powerlog_start_time != 0)
+    {
+        const mDNSBool usesAWDL = ClientRequestUsesAWDL(request->interfaceIndex, request->flags);
+        mdns_powerlog_query_record_stop(request->pid_name, request->powerlog_start_time, usesAWDL);
+        request->powerlog_start_time = 0;
+    }
+#endif
 }
 
 typedef struct
 {
     QueryRecordClientRequestParams cr;
     char qname[MAX_ESCAPED_DOMAIN_NAME];
+    mDNSu8 resolverUUID[MDNS_UUID_SIZE];
 } uds_queryrecord_params_t;
 
 static void _uds_queryrecord_params_init(uds_queryrecord_params_t *const params)
@@ -4552,14 +5084,34 @@
 static void _uds_queryrecord_params_copy(uds_queryrecord_params_t *const dst, const uds_queryrecord_params_t *const src)
 {
 	*dst = *src;
-    dst->cr.qnameStr = dst->qname; // Must point to own qname buffer.
+    // Be careful when copying pointers. Each parameters object should point to its own buffers.
+    dst->cr.qnameStr = dst->qname;
+#if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    if (src->cr.resolverUUID)
+    {
+        dst->cr.resolverUUID = dst->resolverUUID;
+    }
+#endif
 }
 #endif
 
 mDNSlocal mStatus _handle_queryrecord_request_start(request_state *request, const uds_queryrecord_params_t *const params)
 {
     request->terminate = queryrecord_termination_callback;
-    return QueryRecordClientRequestStart(request->queryrecord, &params->cr, queryrecord_result_reply, request);
+    QueryRecordClientRequest *queryrecord = request->queryrecord;
+    const mStatus err = QueryRecordClientRequestStart(queryrecord, &params->cr, queryrecord_result_reply, request);
+#if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
+    if (!err)
+    {
+        const domainname *const qname = QueryRecordClientRequestGetQName(queryrecord);
+        if ((request->interfaceIndex != kDNSServiceInterfaceIndexLocalOnly) && IsLocalDomain(qname))
+        {
+            const mDNSBool usesAWDL = ClientRequestUsesAWDL(request->interfaceIndex, request->flags);
+            request->powerlog_start_time = mdns_powerlog_query_record_start(request->pid_name, usesAWDL);
+        }
+    }
+#endif
+    return err;
 }
 
 #if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
@@ -4636,6 +5188,11 @@
             case mdns_trust_status_denied:
             case mdns_trust_status_pending:
             {
+                if (!_prepare_trusts_for_request(request))
+                {
+                    err = mStatus_NoMemoryErr;
+                    goto exit;
+                }
                 mdns_trust_t trust = mdns_trust_create(*token, service_ptr, flags);
                 if (!trust )
                 {
@@ -4681,7 +5238,8 @@
                         KQueueUnlock("_handle_queryrecord_request_with_trust");
                     }
                 });
-                request->trust = trust;
+                CFArrayAppendValue(request->trusts, trust);
+                mdns_release(trust);
                 mdns_trust_activate(trust);
                 err = mStatus_NoError;
                 break;
@@ -4694,6 +5252,9 @@
             case mdns_trust_status_granted:
                 err = _handle_queryrecord_request_start(request, params);
                 break;
+
+            MDNS_COVERED_SWITCH_DEFAULT:
+                err = mStatus_UnknownErr;
         }
     }
 exit:
@@ -4704,14 +5265,24 @@
 #if MDNSRESPONDER_SUPPORTS(APPLE, IPC_TLV)
 mDNSlocal void get_queryrecord_tlvs(request_state *const request, uds_queryrecord_params_t *const params)
 {
-    if (request->msgptr && (request->hdr.ipc_flags & IPC_FLAGS_TRAILING_TLVS))
-    {
-        const mDNSu8 *const start = (const mDNSu8 *)request->msgptr;
-        const mDNSu8 *const end   = (const mDNSu8 *)request->msgend;
-        const mDNSu32 aaaaPolicy = get_tlv_uint32(start, end, IPC_TLV_TYPE_SERVICE_ATTR_AAAA_POLICY, mDNSNULL);
-        params->cr.useAAAAFallback = (aaaaPolicy == kDNSServiceAAAAPolicyFallback);
-        const mDNSu32 failoverPolicy = get_tlv_uint32(start, end, IPC_TLV_TYPE_SERVICE_ATTR_FAILOVER_POLICY, mDNSNULL);
+    mdns_require_quiet(request->msgptr, exit);
+    mdns_require_quiet(request->hdr.ipc_flags & IPC_FLAGS_TRAILING_TLVS, exit);
+
+    const mDNSu8 *const start = (const mDNSu8 *)request->msgptr;
+    const mDNSu8 *const end   = (const mDNSu8 *)request->msgend;
+    const mDNSu32 aaaaPolicy = get_tlv_uint32(start, end, IPC_TLV_TYPE_SERVICE_ATTR_AAAA_POLICY, mDNSNULL);
+    params->cr.useAAAAFallback = (aaaaPolicy == kDNSServiceAAAAPolicyFallback);
 #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
+    const mDNSu8 *const resolverOverride = get_tlv_uuid(start, end, IPC_TLV_TYPE_RESOLVER_OVERRIDE);
+    if (resolverOverride)
+    {
+        mDNSPlatformMemCopy(params->resolverUUID, resolverOverride, MDNS_UUID_SIZE);
+        params->cr.resolverUUID = params->resolverUUID;
+        params->cr.overrideDNSService = mDNStrue;
+    }
+    else
+    {
+        const mDNSu32 failoverPolicy = get_tlv_uint32(start, end, IPC_TLV_TYPE_SERVICE_ATTR_FAILOVER_POLICY, mDNSNULL);
         params->cr.useFailover = (failoverPolicy == kDNSServiceFailoverPolicyAllow);
         size_t len;
         const mDNSu8 *const data = get_tlv(start, end, IPC_TLV_TYPE_RESOLVER_CONFIG_PLIST_DATA, &len);
@@ -4721,8 +5292,11 @@
             request->custom_service_id = params->cr.customID;
         }
         params->cr.needEncryption = (get_tlv_uint32(start, end, IPC_TLV_TYPE_REQUIRE_PRIVACY, mDNSNULL) != 0);
-#endif
     }
+#endif
+
+exit:
+    return;
 }
 #endif
 
@@ -4787,11 +5361,10 @@
 
     const mDNSBool localDomain = IsLocalDomain(&query_name);
 
-    LogRedact(localDomain ? MDNS_LOG_CATEGORY_MDNS : MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
-        "[R%d] DNSServiceQueryRecord(%X, %d, " PRI_S "(%x), " PUB_DNS_TYPE PUB_S ") START PID[%d](" PUB_S ")",
-        request->request_id, request->flags, request->interfaceIndex, params.qname,
-        mDNS_NonCryptoHash(mDNSNonCryptoHash_FNV1a, query_name.c, DomainNameLength(&query_name)),
-        DNS_TYPE_PARAM(params.cr.qtype), enablesDNSSEC ? ", DNSSEC" : "", request->process_id, request->pid_name);
+    UDS_LOG_CLIENT_REQUEST_WITH_DNSSEC_INFO(localDomain ? MDNS_LOG_CATEGORY_MDNS : MDNS_LOG_CATEGORY_DEFAULT,
+        MDNS_LOG_DEFAULT, "DNSServiceQueryRecord START", &query_name, request, mDNSfalse, enablesDNSSEC,
+        "qname: " PRI_DM_NAME ", qtype: " PUB_DNS_TYPE,
+        DM_NAME_PARAM_NONNULL(&query_name), DNS_TYPE_PARAM(params.cr.qtype));
 
     request->terminate = NULL;
 
@@ -5000,20 +5573,17 @@
 
         if (status == mStatus_NoError)
         {
-            LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
-                "[R%d] DNSServiceReconfirmRecord(%X, %d, " PRI_DM_NAME "(%x), " PUB_DNS_TYPE ") START PID[%d](" PUB_S ")",
-                request->request_id, request->flags, request->interfaceIndex, DM_NAME_PARAM(rr->resrec.name),
-                mDNS_NonCryptoHash(mDNSNonCryptoHash_FNV1a, rr->resrec.name->c, DomainNameLength(rr->resrec.name)),
-                DNS_TYPE_PARAM(rr->resrec.rrtype), request->process_id, request->pid_name);
+            UDS_LOG_CLIENT_REQUEST(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
+                "DNSServiceReconfirmRecord START", rr->resrec.name, request, mDNSfalse,
+                "rr name: " PRI_DM_NAME ", rr type: " PUB_DNS_TYPE, DM_NAME_PARAM(rr->resrec.name),
+                DNS_TYPE_PARAM(rr->resrec.rrtype));
         }
         else
         {
-            LogRedact(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
-                "[R%d] DNSServiceReconfirmRecord(%X, %d, " PRI_DM_NAME "(%x), " PUB_DNS_TYPE ") FAILED PID[%d](" PUB_S ") -- "
-                "status: %d", request->request_id, request->flags, request->interfaceIndex,
-                DM_NAME_PARAM(rr->resrec.name),
-                mDNS_NonCryptoHash(mDNSNonCryptoHash_FNV1a, rr->resrec.name->c, DomainNameLength(rr->resrec.name)),
-                DNS_TYPE_PARAM(rr->resrec.rrtype), request->process_id, request->pid_name, status);
+            UDS_LOG_CLIENT_REQUEST(MDNS_LOG_CATEGORY_MDNS, MDNS_LOG_DEFAULT,
+                "DNSServiceReconfirmRecord FAILED", rr->resrec.name, request, mDNSfalse,
+                "rr name: " PRI_DM_NAME ", rr type: " PUB_DNS_TYPE ", error: %d", DM_NAME_PARAM(rr->resrec.name),
+                DNS_TYPE_PARAM(rr->resrec.rrtype), status);
         }
 
         freeL("AuthRecord/handle_reconfirm_request", rr);
@@ -5089,10 +5659,11 @@
 mDNSlocal void port_mapping_termination_callback(request_state *request)
 {
     request_port_mapping *const pm = request->pm;
-    LogRedact(MDNS_LOG_CATEGORY_NAT, MDNS_LOG_DEFAULT, "[R%d] DNSServiceNATPortMappingCreate(%X, %u, %u, %d) STOP PID[%d](" PUB_S ")",
+    LogRedact(MDNS_LOG_CATEGORY_NAT, MDNS_LOG_DEFAULT,
+        "[R%d] DNSServiceNATPortMappingCreate(%X, %u, %u, %d) STOP PID[%d](" PUB_S ") -- duration: " PUB_TIME_DUR,
         request->request_id, DNSServiceProtocol(pm->NATinfo.Protocol),
         mDNSVal16(pm->NATinfo.IntPort), mDNSVal16(pm->ReqExt), pm->NATinfo.NATLease,
-        request->process_id, request->pid_name);
+        request->process_id, request->pid_name, request_state_get_duration(request));
 
     mDNS_StopNATOperation(&mDNSStorage, &pm->NATinfo);
 }
@@ -5216,12 +5787,20 @@
 mDNSlocal void addrinfo_termination_callback(request_state *request)
 {
     GetAddrInfoClientRequest *const addrinfo = request->addrinfo;
-    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
-           "[R%u] DNSServiceGetAddrInfo(" PRI_DM_NAME ") STOP PID[%d](" PUB_S ")",
-           request->request_id, DM_NAME_PARAM(GetAddrInfoClientRequestGetQName(addrinfo)),
-           request->process_id, request->pid_name);
+
+    UDS_LOG_CLIENT_REQUEST(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
+        "DNSServiceGetAddrInfo STOP", GetAddrInfoClientRequestGetQName(addrinfo), request,
+        mDNStrue, "hostname: " PRI_DM_NAME, DM_NAME_PARAM(GetAddrInfoClientRequestGetQName(addrinfo)));
 
     GetAddrInfoClientRequestStop(addrinfo);
+#if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
+    if (request->powerlog_start_time != 0)
+    {
+        const mDNSBool usesAWDL = ClientRequestUsesAWDL(request->interfaceIndex, request->flags);
+        mdns_powerlog_getaddrinfo_stop(request->pid_name, request->powerlog_start_time, usesAWDL);
+        request->powerlog_start_time = 0;
+    }
+#endif
 }
 
 typedef struct {
@@ -5259,7 +5838,17 @@
     get_tracker_info_tlvs(request);
 #endif
     err = GetAddrInfoClientRequestStart(request->addrinfo, &gaiParams, queryrecord_result_reply, request);
-
+#if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
+    if (!err)
+    {
+        const domainname *const qname = GetAddrInfoClientRequestGetQName(request->addrinfo);
+        if ((request->interfaceIndex != kDNSServiceInterfaceIndexLocalOnly) && IsLocalDomain(qname))
+        {
+            const mDNSBool usesAWDL = ClientRequestUsesAWDL(request->interfaceIndex, request->flags);
+            request->powerlog_start_time = mdns_powerlog_getaddrinfo_start(request->pid_name, usesAWDL);
+        }
+    }
+#endif
     return err;
 }
 
@@ -5288,6 +5877,11 @@
             case mdns_trust_status_denied:
             case mdns_trust_status_pending:
             {
+                if (!_prepare_trusts_for_request(request))
+                {
+                    err = mStatus_NoMemoryErr;
+                    goto exit;
+                }
                 mdns_trust_t trust = mdns_trust_create(*token, NULL, flags);
                 if (!trust )
                 {
@@ -5332,7 +5926,8 @@
                         KQueueUnlock("_handle_addrinfo_request_with_trust");
                     }
                 });
-                request->trust = trust;
+                CFArrayAppendValue(request->trusts, trust);
+                mdns_release(trust);
                 mdns_trust_activate(trust);
                 err = mStatus_NoError;
                 break;
@@ -5345,6 +5940,9 @@
             case mdns_trust_status_granted:
                 err = _handle_addrinfo_request_start(request, params);
                 break;
+
+            MDNS_COVERED_SWITCH_DEFAULT:
+                err = mStatus_UnknownErr;
         }
     }
 exit:
@@ -5408,10 +6006,11 @@
     enablesDNSSEC = dns_service_flags_enables_dnssec(request->flags);
 #endif
 
-    LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
-        "[R%u] DNSServiceGetAddrInfo(%X, %d, %u, " PRI_S PUB_S ") START PID[%d](" PUB_S ")",
-        request->request_id, request->flags, request->interfaceIndex, params.protocols, params.hostname,
-        enablesDNSSEC ? ", DNSSEC" : "", request->process_id, request->pid_name);
+    domainname qname;
+    MakeDomainNameFromDNSNameString(&qname, params.hostname);
+    UDS_LOG_CLIENT_REQUEST_WITH_DNSSEC_INFO(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEFAULT,
+        "DNSServiceGetAddrInfo START", &qname, request, mDNSfalse, enablesDNSSEC,
+        "hostname: " PRI_DM_NAME ", protocols: %u", DM_NAME_PARAM_NONNULL(&qname), params.protocols);
 
     request->terminate = NULL;
 
@@ -5928,6 +6527,8 @@
             newreq->msgptr  = req->msgptr;
             newreq->msgend  = req->msgend;
             newreq->request_id = GetNewRequestID();
+            newreq->request_start_time_secs = 0;
+            newreq->last_full_log_time_secs = 0;
 #if MDNSRESPONDER_SUPPORTS(APPLE, AUDIT_TOKEN)
             mdns_replace(&newreq->peer_token, req->peer_token);
 #endif
@@ -6044,6 +6645,8 @@
         request->sd    = sd;
         request->errsd = sd;
         request->request_id = GetNewRequestID();
+        request->request_start_time_secs = 0;
+        request->last_full_log_time_secs = 0;
         set_peer_pid(request);
         LogRedact(MDNS_LOG_CATEGORY_DEFAULT, MDNS_LOG_DEBUG, "%3d: connect_callback: Adding FD for uid %u", request->sd, request->uid);
         udsSupportAddFDToEventLoop(sd, request_callback, request, &request->platform_data);
@@ -6933,8 +7536,7 @@
                 mDNSu32 *const countPtr = InterfaceID ? &mcastRecordCount : &ucastRecordCount;
 #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
                 const mdns_dns_service_t dnsservice = mdns_cache_metadata_get_dns_service(cr->resrec.metadata);
-                if (!InterfaceID && dnsservice &&
-                    (mdns_dns_service_get_scope(dnsservice) == mdns_dns_service_scope_interface))
+                if (!InterfaceID && dnsservice && mdns_dns_service_is_interface_scoped(dnsservice))
                 {
                     InterfaceID = (mDNSInterfaceID)(uintptr_t)mdns_dns_service_get_interface_index(dnsservice);
                 }
@@ -7336,8 +7938,8 @@
             if (resolve->ReportTime && ((now - resolve->ReportTime) >= 0))
             {
                 resolve->ReportTime = 0;
-                // if client received results and resolve still active
-                if (resolve->txt && resolve->srv)
+                // if client received results (we have both SRV and TXT record) and resolve still active
+                if (resolve_result_is_complete(resolve))
                 {
                     LogMsgNoIdent("Client application PID[%d](%s) has received results for DNSServiceResolve(%##s) yet remains active over two minutes.", r->process_id, r->pid_name, resolve->qsrv.qname.c);
                 }
@@ -7422,7 +8024,7 @@
     // other overly-large structures instead of having a pointer to them, can inadvertently
     // cause structure sizes (and therefore memory usage) to balloon unreasonably.
     char sizecheck_request_state          [(sizeof(request_state)           <= 1072) ? 1 : -1];
-    char sizecheck_registered_record_entry[(sizeof(registered_record_entry) <=   60) ? 1 : -1];
+    char sizecheck_registered_record_entry[(sizeof(registered_record_entry) <=   64) ? 1 : -1];
     char sizecheck_service_instance       [(sizeof(service_instance)        <= 6552) ? 1 : -1];
     char sizecheck_browser_t              [(sizeof(browser_t)               <=  984) ? 1 : -1];
     char sizecheck_reply_hdr              [(sizeof(reply_hdr)               <=   12) ? 1 : -1];
diff --git a/mDNSShared/uds_daemon.h b/mDNSShared/uds_daemon.h
index 0aef6b2..94b343a 100644
--- a/mDNSShared/uds_daemon.h
+++ b/mDNSShared/uds_daemon.h
@@ -1,6 +1,6 @@
 /* -*- Mode: C; tab-width: 4 -*-
  *
- * Copyright (c) 2002-2023 Apple Inc. All rights reserved.
+ * Copyright (c) 2002-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -51,6 +51,9 @@
 
 typedef struct registered_record_entry
 {
+#if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
+    uint64_t powerlog_start_time;
+#endif
     struct registered_record_entry *next;
     mDNSu32 key;
     client_context_t regrec_client_context;
@@ -105,7 +108,7 @@
     DNSQuestion q_default;
     DNSQuestion q_autoall;
 } request_enumeration;
-mdns_compile_time_max_size_check(request_enumeration, 2096);
+mdns_compile_time_max_size_check(request_enumeration, 2144);
 
 typedef struct
 {
@@ -130,12 +133,21 @@
 {
     DNSQuestion qtxt;
     DNSQuestion qsrv;
-    const ResourceRecord *txt;
-    const ResourceRecord *srv;
+
+    domainname *srv_target_name;    // Dynamically allocated SRV rdata(target name)
+    mDNSu8 *txt_rdata;              // Dynamically allocated TXT rdata.
+    mDNSIPPort srv_port;            // The port number specified in the SRV rdata.
+    mDNSu16 txt_rdlength;           // The length of the TXT record rdata.
+
     mDNSs32 ReportTime;
     mDNSBool external_advertise;
+    mDNSBool srv_negative;          // Whether we have received a negative SRV record. If true, srv_target_name is
+                                    // always NULL and srv_port's value has no meaning. When srv_target_name is
+                                    // non-NULL, srv_negative is always false;
+    mDNSBool txt_negative;          // Whether we have received a negative TXT record. If true, txt_rdata is always NULL
+                                    // and txt_rdlength is 0. When txt_rdata is non-NULL, txt_negative is always false.
 } request_resolve;
-mdns_compile_time_max_size_check(request_resolve, 1416);
+mdns_compile_time_max_size_check(request_resolve, 1456);
 
 typedef struct
 {
@@ -165,6 +177,9 @@
 #if MDNSRESPONDER_SUPPORTS(APPLE, QUERIER)
     mdns_dns_service_id_t custom_service_id;
 #endif
+#if MDNSRESPONDER_SUPPORTS(APPLE, POWERLOG_MDNS_REQUESTS)
+    uint64_t powerlog_start_time;
+#endif
     request_state *next;            // For a shared connection, the next element in the list of subordinate
                                     // requests on that connection. Otherwise null.
     request_state *primary;         // For a subordinate request, the request that represents the shared
@@ -175,7 +190,7 @@
 #endif
     void * platform_data;
 #if MDNSRESPONDER_SUPPORTS(APPLE, TRUST_ENFORCEMENT)
-    mdns_trust_t trust;
+    CFMutableArrayRef trusts;
 #endif
 #if MDNSRESPONDER_SUPPORTS(APPLE, SIGNED_RESULTS)
     mdns_signed_result_t signed_obj;
@@ -199,6 +214,8 @@
     dnssd_sock_t errsd;
     mDNSu32 uid;
     mDNSu32 request_id;
+    mDNSs32 request_start_time_secs; // The time when the request is started in continuous time seconds.
+    mDNSs32 last_full_log_time_secs; // The time when we print full log information in continuous time seconds.
     mDNSu32 hdr_bytes;              // bytes of header already read [1]
     ipc_msg_hdr hdr;                // [1]
     mDNSs32 time_blocked;           // record time of a blocked client
@@ -224,7 +241,7 @@
 MDNS_CLANG_TREAT_WARNING_AS_ERROR_END()
 MDNS_GENERAL_STRUCT_PAD_CHECK(struct request_state);
 #endif
-mdns_compile_time_max_size_check(struct request_state, 272);
+mdns_compile_time_max_size_check(struct request_state, 288);
 
 // Notes:
 // 1. On a shared connection these fields in the primary structure, including hdr, are re-used
diff --git a/mDNSShared/utilities/misc_utilities.c b/mDNSShared/utilities/misc_utilities.c
index 49a6159..5c444ee 100644
--- a/mDNSShared/utilities/misc_utilities.c
+++ b/mDNSShared/utilities/misc_utilities.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2021 Apple Inc. All rights reserved.
+ * Copyright (c) 2021-2024 Apple Inc. All rights reserved.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -30,7 +30,7 @@
 mDNSAddr_from_in_addr(const struct in_addr * const NONNULL v4)
 {
 	mDNSAddr mdns_addr; // NOLINT(misc-uninitialized-record-variable): No need to initialize mdns_addr here.
-	check_compile_time_code(sizeof(mdns_addr.ip.v4) == sizeof(v4->s_addr));
+	mdns_compile_time_check_local(sizeof(mdns_addr.ip.v4) == sizeof(v4->s_addr));
 
 	mdns_addr.type = mDNSAddrType_IPv4;
 	mDNSPlatformMemCopy(&mdns_addr.ip.v4, &v4->s_addr, sizeof(v4->s_addr));
@@ -44,7 +44,7 @@
 mDNSAddr_from_in6_addr(const struct in6_addr * const NONNULL v6)
 {
 	mDNSAddr mdns_addr; // NOLINT(misc-uninitialized-record-variable): No need to initialize mdns_addr here.
-	check_compile_time_code(sizeof(mdns_addr.ip.v6) == sizeof(v6->s6_addr));
+	mdns_compile_time_check_local(sizeof(mdns_addr.ip.v6) == sizeof(v6->s6_addr));
 
 	mdns_addr.type = mDNSAddrType_IPv6;
 	mDNSPlatformMemCopy(&mdns_addr.ip.v6, v6->s6_addr, sizeof(v6->s6_addr));