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