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