/* * * Copyright (c) 2021 Project CHIP Authors * * 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. */ #include "Resolver.h" #include #include #include #include #include #include #include #include #include #include #include #include #include // MDNS servers will receive all broadcast packets over the network. // Disable 'invalid packet' messages because the are expected and common // These logs are useful for debug only #undef MINMDNS_RESOLVER_OVERLY_VERBOSE namespace chip { namespace Dnssd { namespace { constexpr size_t kMdnsMaxPacketSize = 1024; constexpr uint16_t kMdnsPort = 5353; using namespace mdns::Minimal; /// Handles processing of minmdns packet data. /// /// Can process multiple incremental resolves based on SRV data and allows /// retrieval of pending (e.g. to ask for AAAA) and complete data items. /// class PacketParser : private ParserDelegate { public: PacketParser(ActiveResolveAttempts & activeResolves) : mActiveResolves(activeResolves) {} /// Goes through the given SRV records within a response packet /// and sets up data resolution void ParseSrvRecords(const BytesRange & packet); /// Goes through non-SRV records and feeds them through the initialized /// SRV record parsing. /// /// Must be called AFTER ParseSrvRecords has been called. void ParseNonSrvRecords(Inet::InterfaceId interface, const BytesRange & packet); IncrementalResolver * ResolverBegin() { return mResolvers; } IncrementalResolver * ResolverEnd() { return mResolvers + kMinMdnsNumParallelResolvers; } private: // ParserDelegate implementation void OnHeader(ConstHeaderRef & header) override; void OnQuery(const QueryData & data) override; void OnResource(ResourceType type, const ResourceData & data) override; /// Called IFF data is of SRV type and we are in SRV initialization state /// /// Initializes a resolver with the given SRV content as long as /// inactive resolvers exist. void ParseSRVResource(const ResourceData & data); /// Called IFF parsing state is in RecordParsing /// /// Forwards the resource to all active resolvers. void ParseResource(const ResourceData & data); enum class RecordParsingState { kIdle, kSrvInitialization, kRecordParsing, }; static constexpr size_t kMinMdnsNumParallelResolvers = CHIP_CONFIG_MINMDNS_MAX_PARALLEL_RESOLVES; // Individual parse set bool mIsResponse = false; Inet::InterfaceId mInterfaceId = Inet::InterfaceId::Null(); BytesRange mPacketRange; RecordParsingState mParsingState = RecordParsingState::kIdle; // resolvers kept between parse steps ActiveResolveAttempts & mActiveResolves; IncrementalResolver mResolvers[kMinMdnsNumParallelResolvers]; }; void PacketParser::OnHeader(ConstHeaderRef & header) { mIsResponse = header.GetFlags().IsResponse(); #ifdef MINMDNS_RESOLVER_OVERLY_VERBOSE if (header.GetFlags().IsTruncated()) { // MinMdns does not cache data, so receiving piecewise data does not work ChipLogError(Discovery, "Truncated responses not supported for address resolution"); } #endif } void PacketParser::OnQuery(const QueryData & data) { // Ignore queries: // - unicast answers will include the corresponding query in the answer // packet, however that is not interesting for the resolver. } void PacketParser::OnResource(ResourceType type, const ResourceData & data) { if (!mIsResponse) { return; } switch (mParsingState) { case RecordParsingState::kSrvInitialization: { if (data.GetType() != QType::SRV) { return; } mdns::Minimal::Logging::LogReceivedResource(data); ParseSRVResource(data); break; } case RecordParsingState::kRecordParsing: if (data.GetType() != QType::SRV) { // SRV packets logged during 'SrvInitialization' phase mdns::Minimal::Logging::LogReceivedResource(data); } ParseResource(data); break; case RecordParsingState::kIdle: ChipLogError(Discovery, "Illegal state: received DNSSD resource while IDLE"); break; } } void PacketParser::ParseResource(const ResourceData & data) { for (auto & resolver : mResolvers) { if (resolver.IsActive()) { CHIP_ERROR err = resolver.OnRecord(mInterfaceId, data, mPacketRange); // // CHIP_ERROR_NO_MEMORY usually gets returned when we have no more memory available to hold the // resolved data. This gets emitted fairly frequently in dense environments or when receiving records // from devices with lots of interfaces. Consequently, don't log that unless we have DNS verbosity // logging enabled. // if (err != CHIP_NO_ERROR) { #if !CHIP_MINMDNS_HIGH_VERBOSITY if (err != CHIP_ERROR_NO_MEMORY) #endif ChipLogError(Discovery, "DNSSD parse error: %" CHIP_ERROR_FORMAT, err.Format()); } } } // Once an IP address is received, stop requesting it. if (data.GetType() == QType::AAAA) { mActiveResolves.CompleteIpResolution(data.GetName()); } } void PacketParser::ParseSRVResource(const ResourceData & data) { SrvRecord srv; if (!srv.Parse(data.GetData(), mPacketRange)) { ChipLogError(Discovery, "Packet data reporter failed to parse SRV record"); return; } for (auto & resolver : mResolvers) { if (resolver.IsActive() && (resolver.GetRecordName() == data.GetName())) { ChipLogDetail(Discovery, "SRV record already actively processed."); return; } } for (auto & resolver : mResolvers) { if (resolver.IsActive()) { continue; } CHIP_ERROR err = resolver.InitializeParsing(data.GetName(), data.GetTtlSeconds(), srv); if (err != CHIP_NO_ERROR) { // Receiving records that we do not need to parse is normal: // MinMDNS may receive all DNSSD packets on the network, only // interested in a subset that is matter-specific #ifdef MINMDNS_RESOLVER_OVERLY_VERBOSE ChipLogError(Discovery, "Could not start SRV record processing: %" CHIP_ERROR_FORMAT, err.Format()); #endif } // Done finding an inactive resolver and attempting to use it. return; } #if CHIP_MINMDNS_HIGH_VERBOSITY ChipLogError(Discovery, "Insufficient parsers to process all SRV entries."); #endif } void PacketParser::ParseSrvRecords(const BytesRange & packet) { MATTER_TRACE_SCOPE("Searching SRV Records", "PacketParser"); mParsingState = RecordParsingState::kSrvInitialization; mPacketRange = packet; if (!ParsePacket(packet, this)) { ChipLogError(Discovery, "DNSSD packet parsing failed (for SRV records)"); } mParsingState = RecordParsingState::kIdle; } void PacketParser::ParseNonSrvRecords(Inet::InterfaceId interface, const BytesRange & packet) { MATTER_TRACE_SCOPE("Searching NON-SRV Records", "PacketParser"); mParsingState = RecordParsingState::kRecordParsing; mPacketRange = packet; mInterfaceId = interface; if (!ParsePacket(packet, this)) { ChipLogError(Discovery, "DNSSD packet parsing failed (for non-srv records)"); } mParsingState = RecordParsingState::kIdle; } class MinMdnsResolver : public Resolver, public MdnsPacketDelegate { public: MinMdnsResolver() : mActiveResolves(&chip::System::SystemClock()), mPacketParser(mActiveResolves) { GlobalMinimalMdnsServer::Instance().SetResponseDelegate(this); } ~MinMdnsResolver() { SetDiscoveryContext(nullptr); } //// MdnsPacketDelegate implementation void OnMdnsPacketData(const BytesRange & data, const chip::Inet::IPPacketInfo * info) override; ///// Resolver implementation CHIP_ERROR Init(chip::Inet::EndPointManager * udpEndPointManager) override; bool IsInitialized() override; void Shutdown() override; void SetOperationalDelegate(OperationalResolveDelegate * delegate) override { mOperationalDelegate = delegate; } CHIP_ERROR ResolveNodeId(const PeerId & peerId) override; void NodeIdResolutionNoLongerNeeded(const PeerId & peerId) override; 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; private: OperationalResolveDelegate * mOperationalDelegate = nullptr; DiscoveryContext * mDiscoveryContext = nullptr; System::Layer * mSystemLayer = nullptr; ActiveResolveAttempts mActiveResolves; PacketParser mPacketParser; void SetDiscoveryContext(DiscoveryContext * context); void ScheduleIpAddressResolve(SerializedQNameIterator hostName); CHIP_ERROR SendAllPendingQueries(); CHIP_ERROR ScheduleRetries(); /// Prepare a query for the given schedule attempt CHIP_ERROR BuildQuery(QueryBuilder & builder, const ActiveResolveAttempts::ScheduledAttempt & attempt); /// Prepare a query for specific resolve types CHIP_ERROR BuildQuery(QueryBuilder & builder, const ActiveResolveAttempts::ScheduledAttempt::Browse & data, bool firstSend); CHIP_ERROR BuildQuery(QueryBuilder & builder, const ActiveResolveAttempts::ScheduledAttempt::Resolve & data, bool firstSend); CHIP_ERROR BuildQuery(QueryBuilder & builder, const ActiveResolveAttempts::ScheduledAttempt::IpResolve & data, bool firstSend); /// Clear any incremental resolver that is not waiting for a AAAA address. void ExpireIncrementalResolvers(); void AdvancePendingResolverStates(); static void RetryCallback(System::Layer *, void * self); CHIP_ERROR BrowseNodes(DiscoveryType type, DiscoveryFilter subtype); template mdns::Minimal::FullQName CheckAndAllocateQName(Args &&... parts) { size_t requiredSize = mdns::Minimal::FlatAllocatedQName::RequiredStorageSize(parts...); if (requiredSize > kMaxQnameSize) { return mdns::Minimal::FullQName(); } return mdns::Minimal::FlatAllocatedQName::Build(qnameStorage, parts...); } static constexpr int kMaxQnameSize = 100; char qnameStorage[kMaxQnameSize]; }; void MinMdnsResolver::SetDiscoveryContext(DiscoveryContext * context) { if (mDiscoveryContext != nullptr) { mDiscoveryContext->Release(); } if (context != nullptr) { context->Retain(); } mDiscoveryContext = context; } void MinMdnsResolver::ScheduleIpAddressResolve(SerializedQNameIterator hostName) { HeapQName target(hostName); if (!target.IsOk()) { ChipLogError(Discovery, "Memory allocation error for IP address resolution"); return; } mActiveResolves.MarkPending(ActiveResolveAttempts::ScheduledAttempt::IpResolve(std::move(target))); } void MinMdnsResolver::AdvancePendingResolverStates() { MATTER_TRACE_SCOPE("Advance pending resolve states", "MinMdnsResolver"); for (IncrementalResolver * resolver = mPacketParser.ResolverBegin(); resolver != mPacketParser.ResolverEnd(); resolver++) { if (!resolver->IsActive()) { continue; } IncrementalResolver::RequiredInformationFlags missing = resolver->GetMissingRequiredInformation(); if (missing.Has(IncrementalResolver::RequiredInformationBitFlags::kIpAddress)) { if (resolver->IsActiveCommissionParse()) { // Browse wants IP addresses ScheduleIpAddressResolve(resolver->GetTargetHostName()); } else if (mActiveResolves.ShouldResolveIpAddress(resolver->OperationalParsePeerId())) { // Keep searching for IP addresses if an active resolve needs these IP addresses // otherwise ignore the data (received a SRV record without IP address, however we do not // seem interested in it. Probably just a device that came online). ScheduleIpAddressResolve(resolver->GetTargetHostName()); } else { // This IP address is not interesting enough to run another discovery resolver->ResetToInactive(); } continue; } if (missing.HasAny()) { // Expect either IP missing (ask for it) or done. Anything else is not handled ChipLogError(Discovery, "Unexpected state: cannot advance resolver with missing information"); resolver->ResetToInactive(); continue; } // SUCCESS. Call the delegates if (resolver->IsActiveCommissionParse()) { MATTER_TRACE_SCOPE("Active commissioning delegate call", "MinMdnsResolver"); DiscoveredNodeData nodeData; CHIP_ERROR err = resolver->Take(nodeData); if (err != CHIP_NO_ERROR) { ChipLogError(Discovery, "Failed to take discovery result: %" CHIP_ERROR_FORMAT, err.Format()); continue; } // TODO: Ideally commissioning delegates should be aware of the // node types they receive, however they are currently not // so try to help out by only calling the delegate when an // active browse exists. // // This is NOT ok and probably we should have separate comissioner // or commissionable delegates or pass in a node type argument. bool discoveredNodeIsRelevant = false; switch (resolver->GetCurrentType()) { case IncrementalResolver::ServiceNameType::kCommissioner: discoveredNodeIsRelevant = mActiveResolves.HasBrowseFor(chip::Dnssd::DiscoveryType::kCommissionerNode); break; case IncrementalResolver::ServiceNameType::kCommissionable: discoveredNodeIsRelevant = mActiveResolves.HasBrowseFor(chip::Dnssd::DiscoveryType::kCommissionableNode); break; default: ChipLogError(Discovery, "Unexpected type for browse data parsing"); continue; } if (discoveredNodeIsRelevant) { if (mDiscoveryContext != nullptr) { mDiscoveryContext->OnNodeDiscovered(nodeData); } else { #if CHIP_MINMDNS_HIGH_VERBOSITY ChipLogError(Discovery, "No delegate to report commissioning node discovery"); #endif } } } else if (resolver->IsActiveOperationalParse()) { MATTER_TRACE_SCOPE("Active operational delegate call", "MinMdnsResolver"); ResolvedNodeData nodeResolvedData; CHIP_ERROR err = resolver->Take(nodeResolvedData); if (err != CHIP_NO_ERROR) { ChipLogError(Discovery, "Failed to take NodeData - result: %" CHIP_ERROR_FORMAT, err.Format()); continue; } if (mActiveResolves.HasBrowseFor(chip::Dnssd::DiscoveryType::kOperational)) { if (mDiscoveryContext != nullptr) { DiscoveredNodeData nodeData; OperationalNodeBrowseData opNodeData; opNodeData.peerId = nodeResolvedData.operationalData.peerId; opNodeData.hasZeroTTL = nodeResolvedData.operationalData.hasZeroTTL; nodeData.Set(opNodeData); mDiscoveryContext->OnNodeDiscovered(nodeData); } else { #if CHIP_MINMDNS_HIGH_VERBOSITY ChipLogError(Discovery, "No delegate to report operational node discovery"); #endif } } mActiveResolves.Complete(nodeResolvedData.operationalData.peerId); if (mOperationalDelegate != nullptr) { mOperationalDelegate->OnOperationalNodeResolved(nodeResolvedData); } else { #if CHIP_MINMDNS_HIGH_VERBOSITY ChipLogError(Discovery, "No delegate to report operational node discovery"); #endif } } else { ChipLogError(Discovery, "Unexpected state: record type unknown"); resolver->ResetToInactive(); } } } void MinMdnsResolver::OnMdnsPacketData(const BytesRange & data, const chip::Inet::IPPacketInfo * info) { MATTER_TRACE_SCOPE("Received MDNS Packet", "MinMdnsResolver"); // Fill up any relevant data mPacketParser.ParseSrvRecords(data); mPacketParser.ParseNonSrvRecords(info->Interface, data); AdvancePendingResolverStates(); ScheduleRetries(); } CHIP_ERROR MinMdnsResolver::Init(chip::Inet::EndPointManager * udpEndPointManager) { /// Note: we do not double-check the port as we assume the APP will always use /// the same udpEndPointManager and port for mDNS. mSystemLayer = &udpEndPointManager->SystemLayer(); if (GlobalMinimalMdnsServer::Server().IsListening()) { return CHIP_NO_ERROR; } return GlobalMinimalMdnsServer::Instance().StartServer(udpEndPointManager, kMdnsPort); } bool MinMdnsResolver::IsInitialized() { return GlobalMinimalMdnsServer::Server().IsListening(); } void MinMdnsResolver::Shutdown() { GlobalMinimalMdnsServer::Instance().ShutdownServer(); } CHIP_ERROR MinMdnsResolver::BuildQuery(QueryBuilder & builder, const ActiveResolveAttempts::ScheduledAttempt::Browse & data, bool firstSend) { mdns::Minimal::FullQName qname; switch (data.type) { case DiscoveryType::kOperational: if (data.filter.type == DiscoveryFilterType::kCompressedFabricId) { char subtypeStr[Common::kSubTypeMaxLength + 1]; ReturnErrorOnFailure(MakeServiceSubtype(subtypeStr, sizeof(subtypeStr), data.filter)); qname = CheckAndAllocateQName(subtypeStr, kSubtypeServiceNamePart, kOperationalServiceName, kOperationalProtocol, kLocalDomain); } else { qname = CheckAndAllocateQName(kOperationalServiceName, kOperationalProtocol, kLocalDomain); } break; case DiscoveryType::kCommissionableNode: if (data.filter.type == DiscoveryFilterType::kNone) { qname = CheckAndAllocateQName(kCommissionableServiceName, kCommissionProtocol, kLocalDomain); } else if (data.filter.type == DiscoveryFilterType::kInstanceName) { qname = CheckAndAllocateQName(data.filter.instanceName, kCommissionableServiceName, kCommissionProtocol, kLocalDomain); } else { char subtypeStr[Common::kSubTypeMaxLength + 1]; ReturnErrorOnFailure(MakeServiceSubtype(subtypeStr, sizeof(subtypeStr), data.filter)); qname = CheckAndAllocateQName(subtypeStr, kSubtypeServiceNamePart, kCommissionableServiceName, kCommissionProtocol, kLocalDomain); } break; case DiscoveryType::kCommissionerNode: if (data.filter.type == DiscoveryFilterType::kNone) { qname = CheckAndAllocateQName(kCommissionerServiceName, kCommissionProtocol, kLocalDomain); } else { char subtypeStr[Common::kSubTypeMaxLength + 1]; ReturnErrorOnFailure(MakeServiceSubtype(subtypeStr, sizeof(subtypeStr), data.filter)); qname = CheckAndAllocateQName(subtypeStr, kSubtypeServiceNamePart, kCommissionerServiceName, kCommissionProtocol, kLocalDomain); } break; case DiscoveryType::kUnknown: break; } VerifyOrReturnError(qname.nameCount, CHIP_ERROR_NO_MEMORY); mdns::Minimal::Query query(qname); query .SetClass(QClass::IN) // .SetType(QType::ANY) // .SetAnswerViaUnicast(firstSend) // ; mdns::Minimal::Logging::LogSendingQuery(query); builder.AddQuery(query); return CHIP_NO_ERROR; } CHIP_ERROR MinMdnsResolver::BuildQuery(QueryBuilder & builder, const ActiveResolveAttempts::ScheduledAttempt::Resolve & data, bool firstSend) { char nameBuffer[kMaxOperationalServiceNameSize] = ""; // Node and fabricid are encoded in server names. ReturnErrorOnFailure(MakeInstanceName(nameBuffer, sizeof(nameBuffer), data.peerId)); const char * instanceQName[] = { nameBuffer, kOperationalServiceName, kOperationalProtocol, kLocalDomain }; Query query(instanceQName); query .SetClass(QClass::IN) // .SetType(QType::ANY) // .SetAnswerViaUnicast(firstSend) // ; mdns::Minimal::Logging::LogSendingQuery(query); builder.AddQuery(query); return CHIP_NO_ERROR; } CHIP_ERROR MinMdnsResolver::BuildQuery(QueryBuilder & builder, const ActiveResolveAttempts::ScheduledAttempt::IpResolve & data, bool firstSend) { Query query(data.hostName.Content()); query .SetClass(QClass::IN) // .SetType(QType::AAAA) // .SetAnswerViaUnicast(firstSend) // ; mdns::Minimal::Logging::LogSendingQuery(query); builder.AddQuery(query); return CHIP_NO_ERROR; } CHIP_ERROR MinMdnsResolver::BuildQuery(QueryBuilder & builder, const ActiveResolveAttempts::ScheduledAttempt & attempt) { if (attempt.IsResolve()) { ReturnErrorOnFailure(BuildQuery(builder, attempt.ResolveData(), attempt.firstSend)); } else if (attempt.IsBrowse()) { ReturnErrorOnFailure(BuildQuery(builder, attempt.BrowseData(), attempt.firstSend)); } else if (attempt.IsIpResolve()) { ReturnErrorOnFailure(BuildQuery(builder, attempt.IpResolveData(), attempt.firstSend)); } else { return CHIP_ERROR_INVALID_ARGUMENT; } VerifyOrReturnError(builder.Ok(), CHIP_ERROR_INTERNAL); return CHIP_NO_ERROR; } CHIP_ERROR MinMdnsResolver::SendAllPendingQueries() { while (true) { std::optional resolve = mActiveResolves.NextScheduled(); if (!resolve.has_value()) { break; } System::PacketBufferHandle buffer = System::PacketBufferHandle::New(kMdnsMaxPacketSize); VerifyOrReturnError(!buffer.IsNull(), CHIP_ERROR_NO_MEMORY); QueryBuilder builder(std::move(buffer)); builder.Header().SetMessageId(0); ReturnErrorOnFailure(BuildQuery(builder, *resolve)); if (resolve->firstSend) { ReturnErrorOnFailure(GlobalMinimalMdnsServer::Server().BroadcastUnicastQuery(builder.ReleasePacket(), kMdnsPort)); } else { ReturnErrorOnFailure(GlobalMinimalMdnsServer::Server().BroadcastSend(builder.ReleasePacket(), kMdnsPort)); } } ExpireIncrementalResolvers(); return ScheduleRetries(); } void MinMdnsResolver::ExpireIncrementalResolvers() { // once all queries are sent, if any SRV cannot receive AAAA addresses, expire it for (IncrementalResolver * resolver = mPacketParser.ResolverBegin(); resolver != mPacketParser.ResolverEnd(); resolver++) { if (!resolver->IsActive()) { continue; } IncrementalResolver::RequiredInformationFlags missing = resolver->GetMissingRequiredInformation(); if (missing.Has(IncrementalResolver::RequiredInformationBitFlags::kIpAddress)) { if (mActiveResolves.IsWaitingForIpResolutionFor(resolver->GetTargetHostName())) { continue; } } // mark as expired: not waiting for anything resolver->ResetToInactive(); } } CHIP_ERROR MinMdnsResolver::StartDiscovery(DiscoveryType type, DiscoveryFilter filter, DiscoveryContext & context) { // minmdns currently supports only one discovery context at a time so override the previous context SetDiscoveryContext(&context); return BrowseNodes(type, filter); } CHIP_ERROR MinMdnsResolver::StopDiscovery(DiscoveryContext & context) { SetDiscoveryContext(nullptr); return mActiveResolves.CompleteAllBrowses(); } CHIP_ERROR MinMdnsResolver::ReconfirmRecord(const char * hostname, Inet::IPAddress address, Inet::InterfaceId interfaceId) { return CHIP_ERROR_NOT_IMPLEMENTED; } CHIP_ERROR MinMdnsResolver::BrowseNodes(DiscoveryType type, DiscoveryFilter filter) { mActiveResolves.MarkPending(filter, type); return SendAllPendingQueries(); } CHIP_ERROR MinMdnsResolver::ResolveNodeId(const PeerId & peerId) { mActiveResolves.MarkPending(peerId); return SendAllPendingQueries(); } void MinMdnsResolver::NodeIdResolutionNoLongerNeeded(const PeerId & peerId) { mActiveResolves.NodeIdResolutionNoLongerNeeded(peerId); } CHIP_ERROR MinMdnsResolver::ScheduleRetries() { MATTER_TRACE_SCOPE("Schedule retries", "MinMdnsResolver"); VerifyOrReturnError(mSystemLayer != nullptr, CHIP_ERROR_INCORRECT_STATE); mSystemLayer->CancelTimer(&RetryCallback, this); std::optional delay = mActiveResolves.GetTimeUntilNextExpectedResponse(); if (!delay.has_value()) { return CHIP_NO_ERROR; } return mSystemLayer->StartTimer(*delay, &RetryCallback, this); } void MinMdnsResolver::RetryCallback(System::Layer *, void * self) { reinterpret_cast(self)->SendAllPendingQueries(); } MinMdnsResolver gResolver; } // namespace #if CHIP_DNSSD_DEFAULT_MINIMAL Resolver & GetDefaultResolver() { return gResolver; } #endif // CHIP_DNSSD_DEFAULT_MINIMAL } // namespace Dnssd } // namespace chip