diff --git a/src/lib/dnssd/Discovery_ImplPlatform.cpp b/src/lib/dnssd/Discovery_ImplPlatform.cpp index f4a524f2c81f31..5d1308a2944f02 100644 --- a/src/lib/dnssd/Discovery_ImplPlatform.cpp +++ b/src/lib/dnssd/Discovery_ImplPlatform.cpp @@ -50,7 +50,17 @@ static void HandleNodeResolve(void * context, DnssdService * result, const Span< } DiscoveredNodeData nodeData; - result->ToDiscoveredNodeData(addresses, nodeData); + + if (0 == strncmp(result->mType, kOperationalServiceName, sizeof(kOperationalServiceName))) + { + result->ToDiscoveredOperationalNodeBrowseData(nodeData); + + nodeData.Get().LogDetail(); + discoveryContext->OnNodeDiscovered(nodeData); + return; + } + + result->ToDiscoveredCommissionNodeData(addresses, nodeData); nodeData.Get().LogDetail(); discoveryContext->OnNodeDiscovered(nodeData); @@ -75,8 +85,9 @@ static void HandleNodeBrowse(void * context, DnssdService * services, size_t ser auto & ipAddress = services[i].mAddress; - // Check if SRV, TXT and AAAA records were received in DNS responses - if (strlen(services[i].mHostName) == 0 || services[i].mTextEntrySize == 0 || !ipAddress.has_value()) + // Check that this is not operational browse result and if SRV, TXT and AAAA records were received in DNS responses + if (strncmp(services[i].mType, kOperationalServiceName, sizeof(kOperationalServiceName)) != 0 + && (strlen(services[i].mHostName) == 0 || services[i].mTextEntrySize == 0 || !ipAddress.has_value())) { ChipDnssdResolve(&services[i], services[i].mInterface, HandleNodeResolve, context); } @@ -340,7 +351,15 @@ void DiscoveryImplPlatform::HandleNodeIdResolve(void * context, DnssdService * r impl->mOperationalDelegate->OnOperationalNodeResolved(nodeData); } -void DnssdService::ToDiscoveredNodeData(const Span & addresses, DiscoveredNodeData & nodeData) +void DnssdService::ToDiscoveredOperationalNodeBrowseData(DiscoveredNodeData & nodeData) +{ + nodeData.Set(); + + ExtractIdFromInstanceName(mName, &nodeData.Get().peerId); + nodeData.Get().hasZeroTTL = (mTtlSeconds == 0); +} + +void DnssdService::ToDiscoveredCommissionNodeData(const Span & addresses, DiscoveredNodeData & nodeData) { nodeData.Set(); auto & discoveredData = nodeData.Get(); @@ -746,6 +765,31 @@ CHIP_ERROR DiscoveryImplPlatform::DiscoverCommissioners(DiscoveryFilter filter, return error; } +CHIP_ERROR DiscoveryImplPlatform::DiscoverOperational(DiscoveryFilter filter, DiscoveryContext & context) +{ + ReturnErrorOnFailure(InitImpl()); + StopDiscovery(context); + + char serviceName[kMaxCommissionerServiceNameSize]; + ReturnErrorOnFailure(MakeServiceTypeName(serviceName, sizeof(serviceName), filter, DiscoveryType::kOperational)); + + intptr_t browseIdentifier; + // Increase the reference count of the context to keep it alive until HandleNodeBrowse is called back. + CHIP_ERROR error = ChipDnssdBrowse(serviceName, DnssdServiceProtocol::kDnssdProtocolTcp, Inet::IPAddressType::kAny, + Inet::InterfaceId::Null(), HandleNodeBrowse, context.Retain(), &browseIdentifier); + + if (error == CHIP_NO_ERROR) + { + context.SetBrowseIdentifier(browseIdentifier); + } + else + { + context.Release(); + } + + return error; +} + CHIP_ERROR DiscoveryImplPlatform::StartDiscovery(DiscoveryType type, DiscoveryFilter filter, DiscoveryContext & context) { switch (type) @@ -755,7 +799,7 @@ CHIP_ERROR DiscoveryImplPlatform::StartDiscovery(DiscoveryType type, DiscoveryFi case DiscoveryType::kCommissionerNode: return DiscoverCommissioners(filter, context); case DiscoveryType::kOperational: - return CHIP_ERROR_NOT_IMPLEMENTED; + return DiscoverOperational(filter, context); default: return CHIP_ERROR_INVALID_ARGUMENT; } diff --git a/src/lib/dnssd/Discovery_ImplPlatform.h b/src/lib/dnssd/Discovery_ImplPlatform.h index 34fedf831b5ecf..d51d5e758c0268 100644 --- a/src/lib/dnssd/Discovery_ImplPlatform.h +++ b/src/lib/dnssd/Discovery_ImplPlatform.h @@ -55,6 +55,7 @@ class DiscoveryImplPlatform : public ServiceAdvertiser, public Resolver void NodeIdResolutionNoLongerNeeded(const PeerId & peerId) override; CHIP_ERROR DiscoverCommissionableNodes(DiscoveryFilter filter, DiscoveryContext & context); CHIP_ERROR DiscoverCommissioners(DiscoveryFilter filter, DiscoveryContext & context); + CHIP_ERROR DiscoverOperational(DiscoveryFilter filter, DiscoveryContext & context); CHIP_ERROR StartDiscovery(DiscoveryType type, DiscoveryFilter filter, DiscoveryContext & context) override; CHIP_ERROR StopDiscovery(DiscoveryContext & context) override; CHIP_ERROR ReconfirmRecord(const char * hostname, Inet::IPAddress address, Inet::InterfaceId interfaceId) override; diff --git a/src/lib/dnssd/ServiceNaming.cpp b/src/lib/dnssd/ServiceNaming.cpp index 25bdfbf2cbf341..8ec86a1bfb9b2b 100644 --- a/src/lib/dnssd/ServiceNaming.cpp +++ b/src/lib/dnssd/ServiceNaming.cpp @@ -162,6 +162,10 @@ CHIP_ERROR MakeServiceTypeName(char * buffer, size_t bufferLen, DiscoveryFilter { requiredSize = snprintf(buffer, bufferLen, kCommissionerServiceName); } + else if (type == DiscoveryType::kOperational) + { + requiredSize = snprintf(buffer, bufferLen, kOperationalServiceName); + } else { return CHIP_ERROR_NOT_IMPLEMENTED; diff --git a/src/lib/dnssd/platform/Dnssd.h b/src/lib/dnssd/platform/Dnssd.h index 02fb851a12d861..e1e44053640617 100644 --- a/src/lib/dnssd/platform/Dnssd.h +++ b/src/lib/dnssd/platform/Dnssd.h @@ -81,7 +81,8 @@ struct DnssdService // Time to live in seconds. Per rfc6762 section 10, because we have a hostname, our default TTL is 120 seconds uint32_t mTtlSeconds = 120; - void ToDiscoveredNodeData(const Span & addresses, DiscoveredNodeData & nodeData); + void ToDiscoveredCommissionNodeData(const Span & addresses, DiscoveredNodeData & nodeData); + void ToDiscoveredOperationalNodeBrowseData(DiscoveredNodeData & nodeData); }; /** diff --git a/src/lib/shell/commands/Dns.cpp b/src/lib/shell/commands/Dns.cpp index c6a93657ca7440..d498152f9c1611 100644 --- a/src/lib/shell/commands/Dns.cpp +++ b/src/lib/shell/commands/Dns.cpp @@ -255,6 +255,13 @@ CHIP_ERROR BrowseOperationalHandler(int argc, char ** argv) return sResolverProxy.DiscoverOperationalNodes(filter); } +CHIP_ERROR BrowseStopHandler(int argc, char ** argv) +{ + streamer_printf(streamer_get(), "Stopping browse...\r\n"); + + return sResolverProxy.StopDiscovery(); +} + CHIP_ERROR BrowseHandler(int argc, char ** argv) { if (argc == 0) @@ -289,14 +296,17 @@ void RegisterDnsCommands() "Browse Matter commissionable nodes. Usage: dns browse commissionable [subtype]" }, { &BrowseCommissionerHandler, "commissioner", "Browse Matter commissioner nodes. Usage: dns browse commissioner [subtype]" }, - { &BrowseOperationalHandler, "operational", "Browse Matter operational nodes. Usage: dns browse operational" }, + { &BrowseOperationalHandler, "operational", + "Browse Matter operational nodes. Usage: dns browse operational" }, + { &BrowseStopHandler, "stop", + "Stop ongoing browse. USage: dns browse stop"}, }; static const shell_command_t sDnsSubCommands[] = { { &ResolveHandler, "resolve", "Resolve the DNS service. Usage: dns resolve (e.g. dns resolve 5544332211 1)" }, { &BrowseHandler, "browse", - "Browse DNS services published by Matter nodes. Usage: dns browse " }, + "Browse DNS services published by Matter nodes. Usage: dns browse |stop" }, }; static const shell_command_t sDnsCommand = { &DnsHandler, "dns", "Dns client commands" }; diff --git a/src/platform/Darwin/DnssdContexts.cpp b/src/platform/Darwin/DnssdContexts.cpp index 7dc5956845414f..8eb199a45d6822 100644 --- a/src/platform/Darwin/DnssdContexts.cpp +++ b/src/platform/Darwin/DnssdContexts.cpp @@ -607,7 +607,7 @@ bool ResolveContext::TryReportingResultsForInterfaceIndex(uint32_t interfaceInde { auto delegate = static_cast(context); DiscoveredNodeData nodeData; - service.ToDiscoveredNodeData(addresses, nodeData); + service.ToDiscoveredCommissionNodeData(addresses, nodeData); delegate->OnNodeDiscovered(nodeData); } else diff --git a/src/platform/Linux/DnssdImpl.cpp b/src/platform/Linux/DnssdImpl.cpp index 800064f52157de..d6a210db040e08 100644 --- a/src/platform/Linux/DnssdImpl.cpp +++ b/src/platform/Linux/DnssdImpl.cpp @@ -689,6 +689,11 @@ void CopyTypeWithoutProtocol(char (&dest)[N], const char * typeAndProtocol) void MdnsAvahi::BrowseRetryCallback(chip::System::Layer * aLayer, void * appState) { BrowseContext * context = static_cast(appState); + + // free existing browser handle + avahi_service_browser_free(context->mBrowser); + context->mBrowser = nullptr; + // Don't schedule anything new if we've stopped. if (context->mStopped.load()) { @@ -743,13 +748,20 @@ void MdnsAvahi::HandleBrowse(AvahiServiceBrowser * browser, AvahiIfIndex interfa case AVAHI_BROWSER_ALL_FOR_NOW: { ChipLogProgress(DeviceLayer, "Avahi browse: all for now"); bool needRetries = context->mBrowseRetries++ < kMaxBrowseRetries && !context->mStopped.load(); + // If we were already asked to stop, no need to send a callback - no one is listening. if (!context->mStopped.load()) { context->mCallback(context->mContext, context->mServices.data(), context->mServices.size(), !needRetries, CHIP_NO_ERROR); + + // Clearing records/services already passed to application through delegate. Keeping it may cause + // duplicates in next query / retry attempt as currently found will also come again from cache. + context->mServices.clear(); } - avahi_service_browser_free(browser); + // hold on to browser handle so we don't lose out any message, this will be freed just before next query + context->mBrowser = browser; + if (needRetries) { context->mNextRetryDelay *= 2; @@ -757,6 +769,18 @@ void MdnsAvahi::HandleBrowse(AvahiServiceBrowser * browser, AvahiIfIndex interfa // triggering this function, or it will delete and not reschedule (if stopped). DeviceLayer::SystemLayer().StartTimer(context->mNextRetryDelay / 2, BrowseRetryCallback, context); } + else if (!context->mStopped.load()) + { + // Re-trigger query until StopBrowse() is called + + // Clear retry info and services. + context->mNextRetryDelay = chip::System::Clock::Seconds16(1); + context->mBrowseRetries = 0; + context->mServices.clear(); + // Hand the ownership of the context over to the timer. It will either schedule a new browse on the context, + // triggering this function, or it will delete and not reschedule (if stopped). + DeviceLayer::SystemLayer().StartTimer(context->mNextQueryDelay, BrowseRetryCallback, context); + } else { // We didn't schedule a timer, so we're responsible for deleting the context @@ -772,7 +796,24 @@ void MdnsAvahi::HandleBrowse(AvahiServiceBrowser * browser, AvahiIfIndex interfa std::remove_if(context->mServices.begin(), context->mServices.end(), [name, type](const DnssdService & service) { return strcmp(name, service.mName) == 0 && type == GetFullType(service.mType, service.mProtocol); })); + + DnssdService service = {}; + + Platform::CopyString(service.mName, name); + CopyTypeWithoutProtocol(service.mType, type); + service.mProtocol = GetProtocolInType(type); + service.mAddressType = context->mAddressType; + service.mTransportType = ToAddressType(protocol); + service.mInterface = Inet::InterfaceId::Null(); + if (interface != AVAHI_IF_UNSPEC) + { + service.mInterface = static_cast(interface); + } + service.mType[kDnssdTypeMaxSize] = 0; + service.mTtlSeconds = 0; + context->mCallback(context->mContext, &service, 1, false, CHIP_NO_ERROR); } + break; case AVAHI_BROWSER_CACHE_EXHAUSTED: ChipLogProgress(DeviceLayer, "Avahi browse: cache exhausted"); diff --git a/src/platform/Linux/DnssdImpl.h b/src/platform/Linux/DnssdImpl.h index c66a8c23f700b6..7fda0cc60a1840 100644 --- a/src/platform/Linux/DnssdImpl.h +++ b/src/platform/Linux/DnssdImpl.h @@ -135,7 +135,9 @@ class MdnsAvahi AvahiIfIndex mInterface; std::string mProtocol; chip::System::Clock::Timeout mNextRetryDelay = chip::System::Clock::Seconds16(1); + chip::System::Clock::Timeout mNextQueryDelay = chip::System::Clock::Seconds16(60); std::atomic_bool mStopped{ false }; + AvahiServiceBrowser *mBrowser; }; struct ResolveContext