Line data Source code
1 : /*
2 : *
3 : * Copyright (c) 2022 Project CHIP Authors
4 : *
5 : * Licensed under the Apache License, Version 2.0 (the "License");
6 : * you may not use this file except in compliance with the License.
7 : * You may obtain a copy of the License at
8 : *
9 : * http://www.apache.org/licenses/LICENSE-2.0
10 : *
11 : * Unless required by applicable law or agreed to in writing, software
12 : * distributed under the License is distributed on an "AS IS" BASIS,
13 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 : * See the License for the specific language governing permissions and
15 : * limitations under the License.
16 : */
17 :
18 : #include <lib/address_resolve/AddressResolve_DefaultImpl.h>
19 :
20 : #include <lib/address_resolve/TracingStructs.h>
21 : #include <tracing/macros.h>
22 : #include <transport/raw/PeerAddress.h>
23 :
24 : namespace chip {
25 : namespace AddressResolve {
26 : namespace Impl {
27 : namespace {
28 :
29 : static constexpr System::Clock::Timeout kInvalidTimeout{ System::Clock::Timeout::max() };
30 :
31 : } // namespace
32 :
33 5 : void NodeLookupHandle::ResetForLookup(System::Clock::Timestamp now, const NodeLookupRequest & request)
34 : {
35 5 : mRequestStartTime = now;
36 5 : mRequest = request;
37 5 : mResults = NodeLookupResults();
38 5 : }
39 :
40 29 : void NodeLookupHandle::LookupResult(const ResolveResult & result)
41 : {
42 : MATTER_LOG_NODE_DISCOVERED(Tracing::DiscoveryInfoType::kIntermediateResult, &GetRequest().GetPeerId(), &result);
43 :
44 29 : auto score = Dnssd::IPAddressSorter::ScoreIpAddress(result.address.GetIPAddress(), result.address.GetInterface());
45 29 : [[maybe_unused]] bool success = mResults.UpdateResults(result, score);
46 :
47 : #if CHIP_PROGRESS_LOGGING
48 : char addr_string[Transport::PeerAddress::kMaxToStringSize];
49 29 : result.address.ToString(addr_string);
50 :
51 29 : const PeerId peerId = GetRequest().GetPeerId();
52 29 : if (success)
53 : {
54 20 : ChipLogProgress(Discovery, "%s: new best score: %u (for " ChipLogFormatPeerId ")", addr_string, to_underlying(score),
55 : ChipLogValuePeerId(peerId));
56 : }
57 : else
58 : {
59 9 : ChipLogProgress(Discovery, "%s: score has not improved: %u (for " ChipLogFormatPeerId ")", addr_string,
60 : to_underlying(score), ChipLogValuePeerId(peerId));
61 : }
62 : #endif
63 29 : }
64 :
65 0 : System::Clock::Timeout NodeLookupHandle::NextEventTimeout(System::Clock::Timestamp now)
66 : {
67 0 : const System::Clock::Timestamp elapsed = now - mRequestStartTime;
68 :
69 0 : if (elapsed < mRequest.GetMinLookupTime())
70 : {
71 0 : return mRequest.GetMinLookupTime() - elapsed;
72 : }
73 :
74 0 : if (HasLookupResult())
75 : {
76 : // We can get here if we got our result before our min lookup time had
77 : // elapsed, but time has passed between then and this attempt to re-arm
78 : // the timer, such that now we are past our min lookup time. For
79 : // example, this can happen because the timer is a bit delayed in firing
80 : // but is now being re-scheduled due to a cancellation of a lookup or
81 : // start of a new lookup. Or it could happen because
82 : // OnOperationalNodeResolved got called close to our min lookup time,
83 : // and we crossed that line while going through mActiveLookups and
84 : // before we got to calling ReArmTimer.
85 : //
86 : // In this case, we should just fire the timer ASAP, since our min
87 : // lookup time has elapsed and we have results.
88 0 : return System::Clock::Timeout::zero();
89 : }
90 :
91 0 : if (elapsed < mRequest.GetMaxLookupTime())
92 : {
93 0 : return mRequest.GetMaxLookupTime() - elapsed;
94 : }
95 :
96 0 : ChipLogError(Discovery, "Unexpected timeout: lookup should have been cleaned already.");
97 0 : return System::Clock::Timeout::zero();
98 : }
99 :
100 0 : NodeLookupAction NodeLookupHandle::NextAction(System::Clock::Timestamp now)
101 : {
102 0 : const System::Clock::Timestamp elapsed = now - mRequestStartTime;
103 :
104 0 : ChipLogProgress(Discovery, "Checking node lookup status for " ChipLogFormatPeerId " after %lu ms",
105 : ChipLogValuePeerId(mRequest.GetPeerId()), static_cast<unsigned long>(elapsed.count()));
106 :
107 : // We are still within the minimal search time. Wait for more results.
108 0 : if (elapsed < mRequest.GetMinLookupTime())
109 : {
110 0 : ChipLogProgress(Discovery, "Keeping DNSSD lookup active");
111 0 : return NodeLookupAction::KeepSearching();
112 : }
113 :
114 : // Minimal time to search reached. If any IP available, ready to return it.
115 0 : if (HasLookupResult())
116 : {
117 0 : auto result = TakeLookupResult();
118 0 : return NodeLookupAction::Success(result);
119 : }
120 :
121 : // Give up if the maximum search time has been reached
122 0 : if (elapsed >= mRequest.GetMaxLookupTime())
123 : {
124 0 : return NodeLookupAction::Error(CHIP_ERROR_TIMEOUT);
125 : }
126 :
127 0 : return NodeLookupAction::KeepSearching();
128 : }
129 :
130 48 : bool NodeLookupResults::UpdateResults(const ResolveResult & result, const Dnssd::IPAddressSorter::IpScore newScore)
131 : {
132 48 : Transport::PeerAddress addressWithAdjustedInterface = result.address;
133 48 : if (!addressWithAdjustedInterface.GetIPAddress().IsIPv6LinkLocal())
134 : {
135 : // Only use the DNS-SD resolution's InterfaceID for addresses that are IPv6 LLA.
136 : // For all other addresses, we should rely on the device's routing table to route messages sent.
137 : // Forcing messages down an InterfaceId might fail. For example, in bridged networks like Thread,
138 : // mDNS advertisements are not usually received on the same interface the peer is reachable on.
139 42 : addressWithAdjustedInterface.SetInterface(Inet::InterfaceId::Null());
140 42 : ChipLogDetail(Discovery, "Lookup clearing interface for non LL address");
141 : }
142 :
143 48 : uint8_t insertAtIndex = 0;
144 125 : for (; insertAtIndex < kNodeLookupResultsLen; insertAtIndex++)
145 : {
146 119 : if (insertAtIndex >= count)
147 : {
148 : // This is a new entry.
149 23 : break;
150 : }
151 :
152 96 : auto & oldAddress = results[insertAtIndex].address;
153 :
154 96 : if (oldAddress == addressWithAdjustedInterface)
155 : {
156 : // this address is already in our list.
157 12 : return false;
158 : }
159 :
160 84 : auto oldScore = Dnssd::IPAddressSorter::ScoreIpAddress(oldAddress.GetIPAddress(), oldAddress.GetInterface());
161 84 : if (newScore > oldScore)
162 : {
163 : // This is a score update, it will replace a previous entry.
164 7 : break;
165 : }
166 : }
167 :
168 36 : if (insertAtIndex == kNodeLookupResultsLen)
169 : {
170 6 : return false;
171 : }
172 :
173 : // we are guaranteed no duplicates here:
174 : // - insertAtIndex MUST be with some score that is `< newScore`, so all
175 : // addresses with a `newScore` were duplicate-checked
176 :
177 : // Move the following valid entries one level down.
178 52 : for (auto i = count; i > insertAtIndex; i--)
179 : {
180 22 : if (i >= kNodeLookupResultsLen)
181 : {
182 4 : continue;
183 : }
184 :
185 18 : results[i] = results[i - 1];
186 : }
187 :
188 : // If the number of valid entries is less than the size of the array there is an additional entry.
189 30 : if (count < kNodeLookupResultsLen)
190 : {
191 26 : count++;
192 : }
193 :
194 30 : auto & updatedResult = results[insertAtIndex];
195 30 : updatedResult = result;
196 30 : updatedResult.address = addressWithAdjustedInterface;
197 :
198 30 : return true;
199 : }
200 :
201 0 : CHIP_ERROR Resolver::LookupNode(const NodeLookupRequest & request, Impl::NodeLookupHandle & handle)
202 : {
203 : MATTER_LOG_NODE_LOOKUP(&request);
204 :
205 0 : VerifyOrReturnError(mSystemLayer != nullptr, CHIP_ERROR_INCORRECT_STATE);
206 :
207 0 : handle.ResetForLookup(mTimeSource.GetMonotonicTimestamp(), request);
208 0 : auto & peerId = request.GetPeerId();
209 0 : ReturnErrorOnFailure(Dnssd::Resolver::Instance().ResolveNodeId(peerId));
210 0 : mActiveLookups.PushBack(&handle);
211 0 : ReArmTimer();
212 0 : ChipLogProgress(Discovery, "Lookup started for " ChipLogFormatPeerId, ChipLogValuePeerId(peerId));
213 0 : return CHIP_NO_ERROR;
214 : }
215 :
216 0 : CHIP_ERROR Resolver::TryNextResult(Impl::NodeLookupHandle & handle)
217 : {
218 0 : VerifyOrReturnError(!mActiveLookups.Contains(&handle), CHIP_ERROR_INCORRECT_STATE);
219 0 : VerifyOrReturnError(handle.HasLookupResult(), CHIP_ERROR_NOT_FOUND);
220 :
221 0 : auto listener = handle.GetListener();
222 0 : auto peerId = handle.GetRequest().GetPeerId();
223 0 : auto result = handle.TakeLookupResult();
224 :
225 : MATTER_LOG_NODE_DISCOVERED(Tracing::DiscoveryInfoType::kRetryDifferent, &peerId, &result);
226 :
227 0 : listener->OnNodeAddressResolved(peerId, result);
228 0 : return CHIP_NO_ERROR;
229 : }
230 :
231 0 : CHIP_ERROR Resolver::CancelLookup(Impl::NodeLookupHandle & handle, FailureCallback cancel_method)
232 : {
233 0 : VerifyOrReturnError(handle.IsActive(), CHIP_ERROR_INVALID_ARGUMENT);
234 0 : mActiveLookups.Remove(&handle);
235 0 : Dnssd::Resolver::Instance().NodeIdResolutionNoLongerNeeded(handle.GetRequest().GetPeerId());
236 :
237 : // Adjust any timing updates.
238 0 : ReArmTimer();
239 :
240 : MATTER_LOG_NODE_DISCOVERY_FAILED(&handle.GetRequest().GetPeerId(), CHIP_ERROR_CANCELLED);
241 :
242 0 : if (cancel_method == FailureCallback::Call)
243 : {
244 0 : handle.GetListener()->OnNodeAddressResolutionFailed(handle.GetRequest().GetPeerId(), CHIP_ERROR_CANCELLED);
245 : }
246 :
247 : // TODO: There should be some form of cancel into Dnssd::Resolver::Instance()
248 : // to stop any resolution mechanism if applicable.
249 : //
250 : // Current code just removes the internal list and any callbacks of resolution will
251 : // be ignored. This works from the perspective of the caller of this method,
252 : // but may be wasteful by letting dnssd still work in the background.
253 :
254 0 : return CHIP_NO_ERROR;
255 : }
256 :
257 1 : CHIP_ERROR Resolver::Init(System::Layer * systemLayer)
258 : {
259 1 : mSystemLayer = systemLayer;
260 1 : Dnssd::Resolver::Instance().SetOperationalDelegate(this);
261 1 : return CHIP_NO_ERROR;
262 : }
263 :
264 1 : void Resolver::Shutdown()
265 : {
266 : // mSystemLayer is set in ::Init, so if it's null that means the resolver
267 : // has not been initialized or has already been shut down.
268 1 : VerifyOrReturn(mSystemLayer != nullptr);
269 :
270 1 : while (mActiveLookups.begin() != mActiveLookups.end())
271 : {
272 0 : auto current = mActiveLookups.begin();
273 :
274 0 : const PeerId peerId = current->GetRequest().GetPeerId();
275 0 : NodeListener * listener = current->GetListener();
276 :
277 0 : mActiveLookups.Erase(current);
278 :
279 : MATTER_LOG_NODE_DISCOVERY_FAILED(&peerId, CHIP_ERROR_SHUT_DOWN);
280 :
281 0 : Dnssd::Resolver::Instance().NodeIdResolutionNoLongerNeeded(peerId);
282 : // Failure callback only called after iterator was cleared:
283 : // This allows failure handlers to deallocate structures that may
284 : // contain the active lookup data as a member (intrusive lists members)
285 0 : listener->OnNodeAddressResolutionFailed(peerId, CHIP_ERROR_SHUT_DOWN);
286 : }
287 :
288 : // Re-arm of timer is expected to cancel any active timer as the
289 : // internal list of active lookups is empty at this point.
290 1 : ReArmTimer();
291 :
292 1 : mSystemLayer = nullptr;
293 1 : Dnssd::Resolver::Instance().SetOperationalDelegate(nullptr);
294 : }
295 :
296 0 : void Resolver::OnOperationalNodeResolved(const Dnssd::ResolvedNodeData & nodeData)
297 : {
298 0 : auto it = mActiveLookups.begin();
299 0 : while (it != mActiveLookups.end())
300 : {
301 0 : auto current = it;
302 0 : it++;
303 0 : if (current->GetRequest().GetPeerId() != nodeData.operationalData.peerId)
304 : {
305 0 : continue;
306 : }
307 :
308 0 : ResolveResult result;
309 :
310 0 : result.address.SetPort(nodeData.resolutionData.port);
311 0 : result.address.SetInterface(nodeData.resolutionData.interfaceId);
312 0 : result.mrpRemoteConfig = nodeData.resolutionData.GetRemoteMRPConfig();
313 0 : result.supportsTcpClient = nodeData.resolutionData.supportsTcpClient;
314 0 : result.supportsTcpServer = nodeData.resolutionData.supportsTcpServer;
315 :
316 0 : if (nodeData.resolutionData.isICDOperatingAsLIT.has_value())
317 : {
318 0 : result.isICDOperatingAsLIT = *(nodeData.resolutionData.isICDOperatingAsLIT);
319 : }
320 :
321 0 : for (size_t i = 0; i < nodeData.resolutionData.numIPs; i++)
322 : {
323 : #if !INET_CONFIG_ENABLE_IPV4
324 : if (!nodeData.resolutionData.ipAddress[i].IsIPv6())
325 : {
326 : ChipLogError(Discovery, "Skipping IPv4 address during operational resolve.");
327 : continue;
328 : }
329 : #endif
330 0 : result.address.SetIPAddress(nodeData.resolutionData.ipAddress[i]);
331 0 : current->LookupResult(result);
332 : }
333 :
334 0 : HandleAction(current);
335 : }
336 :
337 0 : ReArmTimer();
338 0 : }
339 :
340 0 : void Resolver::HandleAction(IntrusiveList<NodeLookupHandle>::Iterator & current)
341 : {
342 0 : const NodeLookupAction action = current->NextAction(mTimeSource.GetMonotonicTimestamp());
343 :
344 0 : if (action.Type() == NodeLookupResult::kKeepSearching)
345 : {
346 : // No change in iterator
347 0 : return;
348 : }
349 :
350 : // final result, handle either success or failure
351 0 : const PeerId peerId = current->GetRequest().GetPeerId();
352 0 : NodeListener * listener = current->GetListener();
353 0 : mActiveLookups.Erase(current);
354 :
355 0 : Dnssd::Resolver::Instance().NodeIdResolutionNoLongerNeeded(peerId);
356 :
357 : // ensure action is taken AFTER the current current lookup is marked complete
358 : // This allows failure handlers to deallocate structures that may
359 : // contain the active lookup data as a member (intrusive lists members)
360 0 : switch (action.Type())
361 : {
362 0 : case NodeLookupResult::kLookupError:
363 : MATTER_LOG_NODE_DISCOVERY_FAILED(&peerId, action.ErrorResult());
364 0 : listener->OnNodeAddressResolutionFailed(peerId, action.ErrorResult());
365 0 : break;
366 0 : case NodeLookupResult::kLookupSuccess:
367 : MATTER_LOG_NODE_DISCOVERED(Tracing::DiscoveryInfoType::kResolutionDone, &peerId, &action.ResolveResult());
368 0 : listener->OnNodeAddressResolved(peerId, action.ResolveResult());
369 0 : break;
370 0 : default:
371 0 : ChipLogError(Discovery, "Unexpected lookup state (not success or fail).");
372 0 : break;
373 : }
374 : }
375 :
376 0 : void Resolver::HandleTimer()
377 : {
378 0 : auto it = mActiveLookups.begin();
379 0 : while (it != mActiveLookups.end())
380 : {
381 0 : auto current = it;
382 0 : it++;
383 :
384 0 : HandleAction(current);
385 : }
386 :
387 0 : ReArmTimer();
388 0 : }
389 :
390 0 : void Resolver::OnOperationalNodeResolutionFailed(const PeerId & peerId, CHIP_ERROR error)
391 : {
392 0 : auto it = mActiveLookups.begin();
393 0 : while (it != mActiveLookups.end())
394 : {
395 0 : auto current = it;
396 0 : it++;
397 0 : if (current->GetRequest().GetPeerId() != peerId)
398 : {
399 0 : continue;
400 : }
401 :
402 0 : NodeListener * listener = current->GetListener();
403 0 : mActiveLookups.Erase(current);
404 :
405 0 : Dnssd::Resolver::Instance().NodeIdResolutionNoLongerNeeded(peerId);
406 :
407 : // Failure callback only called after iterator was cleared:
408 : // This allows failure handlers to deallocate structures that may
409 : // contain the active lookup data as a member (intrusive lists members)
410 0 : listener->OnNodeAddressResolutionFailed(peerId, error);
411 : }
412 0 : ReArmTimer();
413 0 : }
414 :
415 1 : void Resolver::ReArmTimer()
416 : {
417 1 : mSystemLayer->CancelTimer(&OnResolveTimer, static_cast<void *>(this));
418 :
419 1 : System::Clock::Timestamp now = mTimeSource.GetMonotonicTimestamp();
420 :
421 1 : System::Clock::Timeout nextTimeout = kInvalidTimeout;
422 1 : for (auto & activeLookup : mActiveLookups)
423 : {
424 0 : System::Clock::Timeout timeout = activeLookup.NextEventTimeout(now);
425 :
426 0 : if (timeout < nextTimeout)
427 : {
428 0 : nextTimeout = timeout;
429 : }
430 : }
431 :
432 1 : if (nextTimeout == kInvalidTimeout)
433 : {
434 : // Generally this is only expected when no active lookups exist
435 1 : return;
436 : }
437 :
438 0 : CHIP_ERROR err = mSystemLayer->StartTimer(nextTimeout, &OnResolveTimer, static_cast<void *>(this));
439 0 : if (err != CHIP_NO_ERROR)
440 : {
441 0 : ChipLogError(Discovery, "Timer schedule error %s assumed permanent", err.AsString());
442 :
443 : // Clear out all active lookups: without timers there is no guarantee of success
444 0 : auto it = mActiveLookups.begin();
445 0 : while (it != mActiveLookups.end())
446 : {
447 0 : const PeerId peerId = it->GetRequest().GetPeerId();
448 0 : NodeListener * listener = it->GetListener();
449 :
450 0 : mActiveLookups.Erase(it);
451 0 : it = mActiveLookups.begin();
452 :
453 0 : Dnssd::Resolver::Instance().NodeIdResolutionNoLongerNeeded(peerId);
454 : // Callback only called after active lookup is cleared
455 : // This allows failure handlers to deallocate structures that may
456 : // contain the active lookup data as a member (intrusive lists members)
457 0 : listener->OnNodeAddressResolutionFailed(peerId, err);
458 : }
459 : }
460 : }
461 :
462 : } // namespace Impl
463 :
464 2 : Resolver & Resolver::Instance()
465 : {
466 2 : static Impl::Resolver gResolver;
467 2 : return gResolver;
468 : }
469 :
470 : } // namespace AddressResolve
471 : } // namespace chip
|