| /* |
| * |
| * Copyright (c) 2022 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 <lib/address_resolve/AddressResolve_DefaultImpl.h> |
| |
| #include <lib/address_resolve/TracingStructs.h> |
| #include <tracing/macros.h> |
| |
| namespace chip { |
| namespace AddressResolve { |
| namespace Impl { |
| namespace { |
| |
| static constexpr System::Clock::Timeout kInvalidTimeout{ System::Clock::Timeout::max() }; |
| |
| } // namespace |
| |
| void NodeLookupHandle::ResetForLookup(System::Clock::Timestamp now, const NodeLookupRequest & request) |
| { |
| mRequestStartTime = now; |
| mRequest = request; |
| mResults = NodeLookupResults(); |
| } |
| |
| void NodeLookupHandle::LookupResult(const ResolveResult & result) |
| { |
| MATTER_LOG_NODE_DISCOVERED(Tracing::DiscoveryInfoType::kIntermediateResult, &GetRequest().GetPeerId(), &result); |
| |
| auto score = Dnssd::IPAddressSorter::ScoreIpAddress(result.address.GetIPAddress(), result.address.GetInterface()); |
| [[maybe_unused]] bool success = mResults.UpdateResults(result, score); |
| |
| #if CHIP_PROGRESS_LOGGING |
| char addr_string[Transport::PeerAddress::kMaxToStringSize]; |
| result.address.ToString(addr_string); |
| |
| if (success) |
| { |
| ChipLogProgress(Discovery, "%s: new best score: %u", addr_string, to_underlying(score)); |
| } |
| else |
| { |
| ChipLogProgress(Discovery, "%s: score has not improved: %u", addr_string, to_underlying(score)); |
| } |
| #endif |
| } |
| |
| System::Clock::Timeout NodeLookupHandle::NextEventTimeout(System::Clock::Timestamp now) |
| { |
| const System::Clock::Timestamp elapsed = now - mRequestStartTime; |
| |
| if (elapsed < mRequest.GetMinLookupTime()) |
| { |
| return mRequest.GetMinLookupTime() - elapsed; |
| } |
| |
| if (HasLookupResult()) |
| { |
| // We can get here if we got our result before our min lookup time had |
| // elapsed, but time has passed between then and this attempt to re-arm |
| // the timer, such that now we are past our min lookup time. For |
| // example, this can happen because the timer is a bit delayed in firing |
| // but is now being re-scheduled due to a cancellation of a lookup or |
| // start of a new lookup. Or it could happen because |
| // OnOperationalNodeResolved got called close to our min lookup time, |
| // and we crossed that line while going through mActiveLookups and |
| // before we got to calling ReArmTimer. |
| // |
| // In this case, we should just fire the timer ASAP, since our min |
| // lookup time has elapsed and we have results. |
| return System::Clock::Timeout::zero(); |
| } |
| |
| if (elapsed < mRequest.GetMaxLookupTime()) |
| { |
| return mRequest.GetMaxLookupTime() - elapsed; |
| } |
| |
| ChipLogError(Discovery, "Unexpected timeout: lookup should have been cleaned already."); |
| return System::Clock::Timeout::zero(); |
| } |
| |
| NodeLookupAction NodeLookupHandle::NextAction(System::Clock::Timestamp now) |
| { |
| const System::Clock::Timestamp elapsed = now - mRequestStartTime; |
| |
| ChipLogProgress(Discovery, "Checking node lookup status after %lu ms", static_cast<unsigned long>(elapsed.count())); |
| |
| // We are still within the minimal search time. Wait for more results. |
| if (elapsed < mRequest.GetMinLookupTime()) |
| { |
| ChipLogProgress(Discovery, "Keeping DNSSD lookup active"); |
| return NodeLookupAction::KeepSearching(); |
| } |
| |
| // Minimal time to search reached. If any IP available, ready to return it. |
| if (HasLookupResult()) |
| { |
| auto result = TakeLookupResult(); |
| return NodeLookupAction::Success(result); |
| } |
| |
| // Give up if the maximum search time has been reached |
| if (elapsed >= mRequest.GetMaxLookupTime()) |
| { |
| return NodeLookupAction::Error(CHIP_ERROR_TIMEOUT); |
| } |
| |
| return NodeLookupAction::KeepSearching(); |
| } |
| |
| bool NodeLookupResults::UpdateResults(const ResolveResult & result, const Dnssd::IPAddressSorter::IpScore newScore) |
| { |
| uint8_t insertAtIndex = 0; |
| for (; insertAtIndex < kNodeLookupResultsLen; insertAtIndex++) |
| { |
| if (insertAtIndex >= count) |
| { |
| // This is a new entry. |
| break; |
| } |
| |
| auto & oldAddress = results[insertAtIndex].address; |
| auto oldScore = Dnssd::IPAddressSorter::ScoreIpAddress(oldAddress.GetIPAddress(), oldAddress.GetInterface()); |
| if (newScore > oldScore) |
| { |
| // This is a score update, it will replace a previous entry. |
| break; |
| } |
| } |
| |
| if (insertAtIndex == kNodeLookupResultsLen) |
| { |
| return false; |
| } |
| |
| // Move the following valid entries one level down. |
| for (auto i = count; i > insertAtIndex; i--) |
| { |
| if (i >= kNodeLookupResultsLen) |
| { |
| continue; |
| } |
| |
| results[i] = results[i - 1]; |
| } |
| |
| // If the number of valid entries is less than the size of the array there is an additional entry. |
| if (count < kNodeLookupResultsLen) |
| { |
| count++; |
| } |
| |
| auto & updatedResult = results[insertAtIndex]; |
| updatedResult = result; |
| if (!updatedResult.address.GetIPAddress().IsIPv6LinkLocal()) |
| { |
| // Only use the DNS-SD resolution's InterfaceID for addresses that are IPv6 LLA. |
| // For all other addresses, we should rely on the device's routing table to route messages sent. |
| // Forcing messages down an InterfaceId might fail. For example, in bridged networks like Thread, |
| // mDNS advertisements are not usually received on the same interface the peer is reachable on. |
| updatedResult.address.SetInterface(Inet::InterfaceId::Null()); |
| ChipLogDetail(Discovery, "Lookup clearing interface for non LL address"); |
| } |
| |
| return true; |
| } |
| |
| CHIP_ERROR Resolver::LookupNode(const NodeLookupRequest & request, Impl::NodeLookupHandle & handle) |
| { |
| MATTER_LOG_NODE_LOOKUP(&request); |
| |
| VerifyOrReturnError(mSystemLayer != nullptr, CHIP_ERROR_INCORRECT_STATE); |
| |
| handle.ResetForLookup(mTimeSource.GetMonotonicTimestamp(), request); |
| ReturnErrorOnFailure(Dnssd::Resolver::Instance().ResolveNodeId(request.GetPeerId())); |
| mActiveLookups.PushBack(&handle); |
| ReArmTimer(); |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR Resolver::TryNextResult(Impl::NodeLookupHandle & handle) |
| { |
| VerifyOrReturnError(!mActiveLookups.Contains(&handle), CHIP_ERROR_INCORRECT_STATE); |
| VerifyOrReturnError(handle.HasLookupResult(), CHIP_ERROR_EMPTY); |
| |
| auto listener = handle.GetListener(); |
| auto peerId = handle.GetRequest().GetPeerId(); |
| auto result = handle.TakeLookupResult(); |
| |
| MATTER_LOG_NODE_DISCOVERED(Tracing::DiscoveryInfoType::kRetryDifferent, &peerId, &result); |
| |
| listener->OnNodeAddressResolved(peerId, result); |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR Resolver::CancelLookup(Impl::NodeLookupHandle & handle, FailureCallback cancel_method) |
| { |
| VerifyOrReturnError(handle.IsActive(), CHIP_ERROR_INVALID_ARGUMENT); |
| mActiveLookups.Remove(&handle); |
| Dnssd::Resolver::Instance().NodeIdResolutionNoLongerNeeded(handle.GetRequest().GetPeerId()); |
| |
| // Adjust any timing updates. |
| ReArmTimer(); |
| |
| MATTER_LOG_NODE_DISCOVERY_FAILED(&handle.GetRequest().GetPeerId(), CHIP_ERROR_CANCELLED); |
| |
| if (cancel_method == FailureCallback::Call) |
| { |
| handle.GetListener()->OnNodeAddressResolutionFailed(handle.GetRequest().GetPeerId(), CHIP_ERROR_CANCELLED); |
| } |
| |
| // TODO: There should be some form of cancel into Dnssd::Resolver::Instance() |
| // to stop any resolution mechanism if applicable. |
| // |
| // Current code just removes the internal list and any callbacks of resolution will |
| // be ignored. This works from the perspective of the caller of this method, |
| // but may be wasteful by letting dnssd still work in the background. |
| |
| return CHIP_NO_ERROR; |
| } |
| |
| CHIP_ERROR Resolver::Init(System::Layer * systemLayer) |
| { |
| mSystemLayer = systemLayer; |
| Dnssd::Resolver::Instance().SetOperationalDelegate(this); |
| return CHIP_NO_ERROR; |
| } |
| |
| void Resolver::Shutdown() |
| { |
| while (mActiveLookups.begin() != mActiveLookups.end()) |
| { |
| auto current = mActiveLookups.begin(); |
| |
| const PeerId peerId = current->GetRequest().GetPeerId(); |
| NodeListener * listener = current->GetListener(); |
| |
| mActiveLookups.Erase(current); |
| |
| MATTER_LOG_NODE_DISCOVERY_FAILED(&peerId, CHIP_ERROR_SHUT_DOWN); |
| |
| Dnssd::Resolver::Instance().NodeIdResolutionNoLongerNeeded(peerId); |
| // Failure callback only called after iterator was cleared: |
| // This allows failure handlers to deallocate structures that may |
| // contain the active lookup data as a member (intrusive lists members) |
| listener->OnNodeAddressResolutionFailed(peerId, CHIP_ERROR_SHUT_DOWN); |
| } |
| |
| // Re-arm of timer is expected to cancel any active timer as the |
| // internal list of active lookups is empty at this point. |
| ReArmTimer(); |
| |
| mSystemLayer = nullptr; |
| Dnssd::Resolver::Instance().SetOperationalDelegate(nullptr); |
| } |
| |
| void Resolver::OnOperationalNodeResolved(const Dnssd::ResolvedNodeData & nodeData) |
| { |
| auto it = mActiveLookups.begin(); |
| while (it != mActiveLookups.end()) |
| { |
| auto current = it; |
| it++; |
| if (current->GetRequest().GetPeerId() != nodeData.operationalData.peerId) |
| { |
| continue; |
| } |
| |
| ResolveResult result; |
| |
| result.address.SetPort(nodeData.resolutionData.port); |
| result.address.SetInterface(nodeData.resolutionData.interfaceId); |
| result.mrpRemoteConfig = nodeData.resolutionData.GetRemoteMRPConfig(); |
| result.supportsTcp = nodeData.resolutionData.supportsTcp; |
| |
| if (nodeData.resolutionData.isICDOperatingAsLIT.HasValue()) |
| { |
| result.isICDOperatingAsLIT = nodeData.resolutionData.isICDOperatingAsLIT.Value(); |
| } |
| |
| for (size_t i = 0; i < nodeData.resolutionData.numIPs; i++) |
| { |
| #if !INET_CONFIG_ENABLE_IPV4 |
| if (!nodeData.resolutionData.ipAddress[i].IsIPv6()) |
| { |
| ChipLogError(Discovery, "Skipping IPv4 address during operational resolve."); |
| continue; |
| } |
| #endif |
| result.address.SetIPAddress(nodeData.resolutionData.ipAddress[i]); |
| current->LookupResult(result); |
| } |
| |
| HandleAction(current); |
| } |
| |
| ReArmTimer(); |
| } |
| |
| void Resolver::HandleAction(IntrusiveList<NodeLookupHandle>::Iterator & current) |
| { |
| const NodeLookupAction action = current->NextAction(mTimeSource.GetMonotonicTimestamp()); |
| |
| if (action.Type() == NodeLookupResult::kKeepSearching) |
| { |
| // No change in iterator |
| return; |
| } |
| |
| // final result, handle either success or failure |
| const PeerId peerId = current->GetRequest().GetPeerId(); |
| NodeListener * listener = current->GetListener(); |
| mActiveLookups.Erase(current); |
| |
| Dnssd::Resolver::Instance().NodeIdResolutionNoLongerNeeded(peerId); |
| |
| // ensure action is taken AFTER the current current lookup is marked complete |
| // This allows failure handlers to deallocate structures that may |
| // contain the active lookup data as a member (intrusive lists members) |
| switch (action.Type()) |
| { |
| case NodeLookupResult::kLookupError: |
| MATTER_LOG_NODE_DISCOVERY_FAILED(&peerId, action.ErrorResult()); |
| listener->OnNodeAddressResolutionFailed(peerId, action.ErrorResult()); |
| break; |
| case NodeLookupResult::kLookupSuccess: |
| MATTER_LOG_NODE_DISCOVERED(Tracing::DiscoveryInfoType::kResolutionDone, &peerId, &action.ResolveResult()); |
| listener->OnNodeAddressResolved(peerId, action.ResolveResult()); |
| break; |
| default: |
| ChipLogError(Discovery, "Unexpected lookup state (not success or fail)."); |
| break; |
| } |
| } |
| |
| void Resolver::HandleTimer() |
| { |
| auto it = mActiveLookups.begin(); |
| while (it != mActiveLookups.end()) |
| { |
| auto current = it; |
| it++; |
| |
| HandleAction(current); |
| } |
| |
| ReArmTimer(); |
| } |
| |
| void Resolver::OnOperationalNodeResolutionFailed(const PeerId & peerId, CHIP_ERROR error) |
| { |
| auto it = mActiveLookups.begin(); |
| while (it != mActiveLookups.end()) |
| { |
| auto current = it; |
| it++; |
| if (current->GetRequest().GetPeerId() != peerId) |
| { |
| continue; |
| } |
| |
| NodeListener * listener = current->GetListener(); |
| mActiveLookups.Erase(current); |
| |
| Dnssd::Resolver::Instance().NodeIdResolutionNoLongerNeeded(peerId); |
| |
| // Failure callback only called after iterator was cleared: |
| // This allows failure handlers to deallocate structures that may |
| // contain the active lookup data as a member (intrusive lists members) |
| listener->OnNodeAddressResolutionFailed(peerId, error); |
| } |
| ReArmTimer(); |
| } |
| |
| void Resolver::ReArmTimer() |
| { |
| mSystemLayer->CancelTimer(&OnResolveTimer, static_cast<void *>(this)); |
| |
| System::Clock::Timestamp now = mTimeSource.GetMonotonicTimestamp(); |
| |
| System::Clock::Timeout nextTimeout = kInvalidTimeout; |
| for (auto & activeLookup : mActiveLookups) |
| { |
| System::Clock::Timeout timeout = activeLookup.NextEventTimeout(now); |
| |
| if (timeout < nextTimeout) |
| { |
| nextTimeout = timeout; |
| } |
| } |
| |
| if (nextTimeout == kInvalidTimeout) |
| { |
| // Generally this is only expected when no active lookups exist |
| return; |
| } |
| |
| CHIP_ERROR err = mSystemLayer->StartTimer(nextTimeout, &OnResolveTimer, static_cast<void *>(this)); |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(Discovery, "Timer schedule error %s assumed permanent", err.AsString()); |
| |
| // Clear out all active lookups: without timers there is no guarantee of success |
| auto it = mActiveLookups.begin(); |
| while (it != mActiveLookups.end()) |
| { |
| const PeerId peerId = it->GetRequest().GetPeerId(); |
| NodeListener * listener = it->GetListener(); |
| |
| mActiveLookups.Erase(it); |
| it = mActiveLookups.begin(); |
| |
| Dnssd::Resolver::Instance().NodeIdResolutionNoLongerNeeded(peerId); |
| // Callback only called after active lookup is cleared |
| // This allows failure handlers to deallocate structures that may |
| // contain the active lookup data as a member (intrusive lists members) |
| listener->OnNodeAddressResolutionFailed(peerId, err); |
| } |
| } |
| } |
| |
| } // namespace Impl |
| |
| Resolver & Resolver::Instance() |
| { |
| static Impl::Resolver gResolver; |
| return gResolver; |
| } |
| |
| } // namespace AddressResolve |
| } // namespace chip |