blob: 6e461a4b99c72d317db838da407fc799b17f3af4 [file] [log] [blame]
/*
*
* 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 for " ChipLogFormatPeerId " after %lu ms",
ChipLogValuePeerId(mRequest.GetPeerId()), 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);
auto & peerId = request.GetPeerId();
ReturnErrorOnFailure(Dnssd::Resolver::Instance().ResolveNodeId(peerId));
mActiveLookups.PushBack(&handle);
ReArmTimer();
ChipLogProgress(Discovery, "Lookup started for " ChipLogFormatPeerId, ChipLogValuePeerId(peerId));
return CHIP_NO_ERROR;
}
CHIP_ERROR Resolver::TryNextResult(Impl::NodeLookupHandle & handle)
{
VerifyOrReturnError(!mActiveLookups.Contains(&handle), CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(handle.HasLookupResult(), CHIP_ERROR_NOT_FOUND);
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