xref: /aosp_15_r20/external/ot-br-posix/src/trel_dnssd/trel_dnssd.cpp (revision 4a64e381480ef79f0532b2421e44e6ee336b8e0d)
1 /*
2  *    Copyright (c) 2021, The OpenThread Authors.
3  *    All rights reserved.
4  *
5  *    Redistribution and use in source and binary forms, with or without
6  *    modification, are permitted provided that the following conditions are met:
7  *    1. Redistributions of source code must retain the above copyright
8  *       notice, this list of conditions and the following disclaimer.
9  *    2. Redistributions in binary form must reproduce the above copyright
10  *       notice, this list of conditions and the following disclaimer in the
11  *       documentation and/or other materials provided with the distribution.
12  *    3. Neither the name of the copyright holder nor the
13  *       names of its contributors may be used to endorse or promote products
14  *       derived from this software without specific prior written permission.
15  *
16  *    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  *    AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  *    IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  *    ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  *    LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  *    CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  *    SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  *    INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  *    CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  *    ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  *    POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 /**
30  * @file
31  *   This file includes implementation of TREL DNS-SD over mDNS.
32  */
33 
34 #if OTBR_ENABLE_TREL
35 
36 #define OTBR_LOG_TAG "TrelDns"
37 
38 #include "trel_dnssd/trel_dnssd.hpp"
39 
40 #include <inttypes.h>
41 #include <net/if.h>
42 
43 #include <openthread/instance.h>
44 #include <openthread/link.h>
45 #include <openthread/platform/trel.h>
46 
47 #include "common/code_utils.hpp"
48 #include "utils/hex.hpp"
49 #include "utils/string_utils.hpp"
50 
51 static const char kTrelServiceName[] = "_trel._udp";
52 
53 static otbr::TrelDnssd::TrelDnssd *sTrelDnssd = nullptr;
54 
trelDnssdInitialize(const char * aTrelNetif)55 void trelDnssdInitialize(const char *aTrelNetif)
56 {
57     sTrelDnssd->Initialize(aTrelNetif);
58 }
59 
trelDnssdStartBrowse(void)60 void trelDnssdStartBrowse(void)
61 {
62     sTrelDnssd->StartBrowse();
63 }
64 
trelDnssdStopBrowse(void)65 void trelDnssdStopBrowse(void)
66 {
67     sTrelDnssd->StopBrowse();
68 }
69 
trelDnssdRegisterService(uint16_t aPort,const uint8_t * aTxtData,uint8_t aTxtLength)70 void trelDnssdRegisterService(uint16_t aPort, const uint8_t *aTxtData, uint8_t aTxtLength)
71 {
72     sTrelDnssd->RegisterService(aPort, aTxtData, aTxtLength);
73 }
74 
trelDnssdRemoveService(void)75 void trelDnssdRemoveService(void)
76 {
77     sTrelDnssd->UnregisterService();
78 }
79 
80 namespace otbr {
81 
82 namespace TrelDnssd {
83 
TrelDnssd(Ncp::RcpHost & aHost,Mdns::Publisher & aPublisher)84 TrelDnssd::TrelDnssd(Ncp::RcpHost &aHost, Mdns::Publisher &aPublisher)
85     : mPublisher(aPublisher)
86     , mHost(aHost)
87 {
88     sTrelDnssd = this;
89 }
90 
Initialize(std::string aTrelNetif)91 void TrelDnssd::Initialize(std::string aTrelNetif)
92 {
93     mTrelNetif = std::move(aTrelNetif);
94     // Reset mTrelNetifIndex to 0 so that when this function is called with a different aTrelNetif
95     // than the current mTrelNetif, CheckTrelNetifReady() will update mTrelNetifIndex accordingly.
96     mTrelNetifIndex = 0;
97 
98     if (IsInitialized())
99     {
100         otbrLogDebug("Initialized on netif \"%s\"", mTrelNetif.c_str());
101         CheckTrelNetifReady();
102     }
103     else
104     {
105         otbrLogDebug("Not initialized");
106     }
107 }
108 
StartBrowse(void)109 void TrelDnssd::StartBrowse(void)
110 {
111     VerifyOrExit(IsInitialized());
112 
113     otbrLogDebug("Start browsing %s services ...", kTrelServiceName);
114 
115     assert(mSubscriberId == 0);
116     mSubscriberId = mPublisher.AddSubscriptionCallbacks(
117         [this](const std::string &aType, const Mdns::Publisher::DiscoveredInstanceInfo &aInstanceInfo) {
118             OnTrelServiceInstanceResolved(aType, aInstanceInfo);
119         },
120         /* aHostCallback */ nullptr);
121 
122     if (IsReady())
123     {
124         mPublisher.SubscribeService(kTrelServiceName, /* aInstanceName */ "");
125     }
126 
127 exit:
128     return;
129 }
130 
StopBrowse(void)131 void TrelDnssd::StopBrowse(void)
132 {
133     VerifyOrExit(IsInitialized());
134 
135     otbrLogDebug("Stop browsing %s service.", kTrelServiceName);
136     assert(mSubscriberId > 0);
137 
138     mPublisher.RemoveSubscriptionCallbacks(mSubscriberId);
139     mSubscriberId = 0;
140 
141     if (IsReady())
142     {
143         mPublisher.UnsubscribeService(kTrelServiceName, "");
144     }
145 
146 exit:
147     return;
148 }
149 
RegisterService(uint16_t aPort,const uint8_t * aTxtData,uint8_t aTxtLength)150 void TrelDnssd::RegisterService(uint16_t aPort, const uint8_t *aTxtData, uint8_t aTxtLength)
151 {
152     assert(aPort > 0);
153     assert(aTxtData != nullptr);
154 
155     VerifyOrExit(IsInitialized());
156 
157     otbrLogDebug("Register %s service: port=%u, TXT=%d bytes", kTrelServiceName, aPort, aTxtLength);
158     otbrDump(OTBR_LOG_DEBUG, OTBR_LOG_TAG, "TXT", aTxtData, aTxtLength);
159 
160     if (mRegisterInfo.IsValid() && IsReady())
161     {
162         UnpublishTrelService();
163     }
164 
165     mRegisterInfo.Assign(aPort, aTxtData, aTxtLength);
166 
167     if (IsReady())
168     {
169         PublishTrelService();
170     }
171 
172 exit:
173     return;
174 }
175 
UnregisterService(void)176 void TrelDnssd::UnregisterService(void)
177 {
178     // Return if service has not been registered
179     VerifyOrExit(IsInitialized() && mRegisterInfo.IsValid());
180 
181     otbrLogDebug("Remove %s service", kTrelServiceName);
182 
183     if (IsReady())
184     {
185         UnpublishTrelService();
186     }
187 
188     mRegisterInfo.Clear();
189 
190 exit:
191     return;
192 }
193 
HandleMdnsState(Mdns::Publisher::State aState)194 void TrelDnssd::HandleMdnsState(Mdns::Publisher::State aState)
195 {
196     VerifyOrExit(aState == Mdns::Publisher::State::kReady);
197 
198     otbrLogDebug("mDNS Publisher is Ready");
199     mMdnsPublisherReady = true;
200     RemoveAllPeers();
201 
202     if (mRegisterInfo.IsPublished())
203     {
204         mRegisterInfo.mInstanceName = "";
205     }
206 
207     VerifyOrExit(IsInitialized());
208     OnBecomeReady();
209 
210 exit:
211     return;
212 }
213 
OnTrelServiceInstanceResolved(const std::string & aType,const Mdns::Publisher::DiscoveredInstanceInfo & aInstanceInfo)214 void TrelDnssd::OnTrelServiceInstanceResolved(const std::string                             &aType,
215                                               const Mdns::Publisher::DiscoveredInstanceInfo &aInstanceInfo)
216 {
217     VerifyOrExit(StringUtils::EqualCaseInsensitive(aType, kTrelServiceName));
218     VerifyOrExit(aInstanceInfo.mNetifIndex == mTrelNetifIndex);
219 
220     if (aInstanceInfo.mRemoved)
221     {
222         OnTrelServiceInstanceRemoved(aInstanceInfo.mName);
223     }
224     else
225     {
226         OnTrelServiceInstanceAdded(aInstanceInfo);
227     }
228 
229 exit:
230     return;
231 }
232 
GetTrelInstanceName(void)233 std::string TrelDnssd::GetTrelInstanceName(void)
234 {
235     const otExtAddress *extaddr = otLinkGetExtendedAddress(mHost.GetInstance());
236     std::string         name;
237     char                nameBuf[sizeof(otExtAddress) * 2 + 1];
238 
239     Utils::Bytes2Hex(extaddr->m8, sizeof(otExtAddress), nameBuf);
240     name = StringUtils::ToLowercase(nameBuf);
241 
242     assert(name.length() == sizeof(otExtAddress) * 2);
243 
244     otbrLogDebug("Using instance name %s", name.c_str());
245     return name;
246 }
247 
PublishTrelService(void)248 void TrelDnssd::PublishTrelService(void)
249 {
250     assert(mRegisterInfo.IsValid());
251     assert(!mRegisterInfo.IsPublished());
252     assert(mTrelNetifIndex > 0);
253 
254     mRegisterInfo.mInstanceName = GetTrelInstanceName();
255     mPublisher.PublishService(/* aHostName */ "", mRegisterInfo.mInstanceName, kTrelServiceName,
256                               Mdns::Publisher::SubTypeList{}, mRegisterInfo.mPort, mRegisterInfo.mTxtData,
257                               [](otbrError aError) { HandlePublishTrelServiceError(aError); });
258 }
259 
HandlePublishTrelServiceError(otbrError aError)260 void TrelDnssd::HandlePublishTrelServiceError(otbrError aError)
261 {
262     if (aError != OTBR_ERROR_NONE)
263     {
264         otbrLogErr("Failed to publish TREL service: %s. TREL won't be working.", otbrErrorString(aError));
265     }
266 }
267 
UnpublishTrelService(void)268 void TrelDnssd::UnpublishTrelService(void)
269 {
270     assert(mRegisterInfo.IsValid());
271     assert(mRegisterInfo.IsPublished());
272 
273     mPublisher.UnpublishService(mRegisterInfo.mInstanceName, kTrelServiceName,
274                                 [](otbrError aError) { HandleUnpublishTrelServiceError(aError); });
275     mRegisterInfo.mInstanceName = "";
276 }
277 
HandleUnpublishTrelServiceError(otbrError aError)278 void TrelDnssd::HandleUnpublishTrelServiceError(otbrError aError)
279 {
280     if (aError != OTBR_ERROR_NONE)
281     {
282         otbrLogInfo("Failed to unpublish TREL service: %s", otbrErrorString(aError));
283     }
284 }
285 
OnTrelServiceInstanceAdded(const Mdns::Publisher::DiscoveredInstanceInfo & aInstanceInfo)286 void TrelDnssd::OnTrelServiceInstanceAdded(const Mdns::Publisher::DiscoveredInstanceInfo &aInstanceInfo)
287 {
288     std::string        instanceName = StringUtils::ToLowercase(aInstanceInfo.mName);
289     Ip6Address         selectedAddress;
290     otPlatTrelPeerInfo peerInfo;
291 
292     // Remove any existing TREL service instance before adding
293     OnTrelServiceInstanceRemoved(instanceName);
294 
295     otbrLogDebug("Peer discovered: %s hostname %s addresses %zu port %d priority %d "
296                  "weight %d",
297                  aInstanceInfo.mName.c_str(), aInstanceInfo.mHostName.c_str(), aInstanceInfo.mAddresses.size(),
298                  aInstanceInfo.mPort, aInstanceInfo.mPriority, aInstanceInfo.mWeight);
299 
300     for (const auto &addr : aInstanceInfo.mAddresses)
301     {
302         otbrLogDebug("Peer address: %s", addr.ToString().c_str());
303 
304         // Skip anycast (Refer to https://datatracker.ietf.org/doc/html/rfc2373#section-2.6.1)
305         if (addr.m64[1] == 0)
306         {
307             continue;
308         }
309 
310         // If there are multiple addresses, we prefer the address
311         // which is numerically smallest. This prefers GUA over ULA
312         // (`fc00::/7`) and then link-local (`fe80::/10`).
313 
314         if (selectedAddress.IsUnspecified() || (addr < selectedAddress))
315         {
316             selectedAddress = addr;
317         }
318     }
319 
320     if (aInstanceInfo.mAddresses.empty())
321     {
322         otbrLogWarning("Peer %s does not have any IPv6 address, ignored", aInstanceInfo.mName.c_str());
323         ExitNow();
324     }
325 
326     peerInfo.mRemoved = false;
327     memcpy(&peerInfo.mSockAddr.mAddress, &selectedAddress, sizeof(peerInfo.mSockAddr.mAddress));
328     peerInfo.mSockAddr.mPort = aInstanceInfo.mPort;
329     peerInfo.mTxtData        = aInstanceInfo.mTxtData.data();
330     peerInfo.mTxtLength      = aInstanceInfo.mTxtData.size();
331 
332     {
333         Peer peer(aInstanceInfo.mTxtData, peerInfo.mSockAddr);
334 
335         VerifyOrExit(peer.mValid, otbrLogWarning("Peer %s is invalid", aInstanceInfo.mName.c_str()));
336 
337         otPlatTrelHandleDiscoveredPeerInfo(mHost.GetInstance(), &peerInfo);
338 
339         mPeers.emplace(instanceName, peer);
340         CheckPeersNumLimit();
341     }
342 
343 exit:
344     return;
345 }
346 
OnTrelServiceInstanceRemoved(const std::string & aInstanceName)347 void TrelDnssd::OnTrelServiceInstanceRemoved(const std::string &aInstanceName)
348 {
349     std::string instanceName = StringUtils::ToLowercase(aInstanceName);
350     auto        it           = mPeers.find(instanceName);
351 
352     VerifyOrExit(it != mPeers.end());
353 
354     otbrLogDebug("Peer removed: %s", instanceName.c_str());
355 
356     // Remove the peer only when all instances are removed because one peer can have multiple instances if expired
357     // instances were not properly removed by mDNS.
358     if (CountDuplicatePeers(it->second) == 0)
359     {
360         NotifyRemovePeer(it->second);
361     }
362 
363     mPeers.erase(it);
364 
365 exit:
366     return;
367 }
368 
CheckPeersNumLimit(void)369 void TrelDnssd::CheckPeersNumLimit(void)
370 {
371     const PeerMap::value_type *oldestPeer = nullptr;
372 
373     VerifyOrExit(mPeers.size() >= kPeerCacheSize);
374 
375     for (const auto &entry : mPeers)
376     {
377         if (oldestPeer == nullptr || entry.second.mDiscoverTime < oldestPeer->second.mDiscoverTime)
378         {
379             oldestPeer = &entry;
380         }
381     }
382 
383     OnTrelServiceInstanceRemoved(oldestPeer->first);
384 
385 exit:
386     return;
387 }
388 
NotifyRemovePeer(const Peer & aPeer)389 void TrelDnssd::NotifyRemovePeer(const Peer &aPeer)
390 {
391     otPlatTrelPeerInfo peerInfo;
392 
393     peerInfo.mRemoved   = true;
394     peerInfo.mTxtData   = aPeer.mTxtData.data();
395     peerInfo.mTxtLength = aPeer.mTxtData.size();
396     peerInfo.mSockAddr  = aPeer.mSockAddr;
397 
398     otPlatTrelHandleDiscoveredPeerInfo(mHost.GetInstance(), &peerInfo);
399 }
400 
RemoveAllPeers(void)401 void TrelDnssd::RemoveAllPeers(void)
402 {
403     for (const auto &entry : mPeers)
404     {
405         NotifyRemovePeer(entry.second);
406     }
407 
408     mPeers.clear();
409 }
410 
CheckTrelNetifReady(void)411 void TrelDnssd::CheckTrelNetifReady(void)
412 {
413     assert(IsInitialized());
414 
415     if (mTrelNetifIndex == 0)
416     {
417         mTrelNetifIndex = if_nametoindex(mTrelNetif.c_str());
418 
419         if (mTrelNetifIndex != 0)
420         {
421             otbrLogDebug("Netif %s is ready: index = %" PRIu32, mTrelNetif.c_str(), mTrelNetifIndex);
422             OnBecomeReady();
423         }
424         else
425         {
426             uint16_t delay = kCheckNetifReadyIntervalMs;
427 
428             otbrLogWarning("Netif %s is not ready (%s), will retry after %d seconds", mTrelNetif.c_str(),
429                            strerror(errno), delay / 1000);
430             mTaskRunner.Post(Milliseconds(delay), [this]() { CheckTrelNetifReady(); });
431         }
432     }
433 }
434 
IsReady(void) const435 bool TrelDnssd::IsReady(void) const
436 {
437     assert(IsInitialized());
438 
439     return mTrelNetifIndex > 0 && mMdnsPublisherReady;
440 }
441 
OnBecomeReady(void)442 void TrelDnssd::OnBecomeReady(void)
443 {
444     if (IsReady())
445     {
446         otbrLogInfo("TREL DNS-SD Is Now Ready: Netif=%s(%" PRIu32 "), SubscriberId=%" PRIu64 ", Register=%s!",
447                     mTrelNetif.c_str(), mTrelNetifIndex, mSubscriberId, mRegisterInfo.mInstanceName.c_str());
448 
449         if (mSubscriberId > 0)
450         {
451             mPublisher.SubscribeService(kTrelServiceName, /* aInstanceName */ "");
452         }
453 
454         if (mRegisterInfo.IsValid())
455         {
456             PublishTrelService();
457         }
458     }
459 }
460 
CountDuplicatePeers(const TrelDnssd::Peer & aPeer)461 uint16_t TrelDnssd::CountDuplicatePeers(const TrelDnssd::Peer &aPeer)
462 {
463     uint16_t count = 0;
464 
465     for (const auto &entry : mPeers)
466     {
467         if (&entry.second == &aPeer)
468         {
469             continue;
470         }
471 
472         if (!memcmp(&entry.second.mSockAddr, &aPeer.mSockAddr, sizeof(otSockAddr)) &&
473             !memcmp(&entry.second.mExtAddr, &aPeer.mExtAddr, sizeof(otExtAddress)))
474         {
475             count++;
476         }
477     }
478 
479     return count;
480 }
481 
Assign(uint16_t aPort,const uint8_t * aTxtData,uint8_t aTxtLength)482 void TrelDnssd::RegisterInfo::Assign(uint16_t aPort, const uint8_t *aTxtData, uint8_t aTxtLength)
483 {
484     assert(!IsPublished());
485     assert(aPort > 0);
486 
487     mPort = aPort;
488     mTxtData.assign(aTxtData, aTxtData + aTxtLength);
489 }
490 
Clear(void)491 void TrelDnssd::RegisterInfo::Clear(void)
492 {
493     assert(!IsPublished());
494 
495     mPort = 0;
496     mTxtData.clear();
497 }
498 
499 const char TrelDnssd::Peer::kTxtRecordExtAddressKey[] = "xa";
500 
ReadExtAddrFromTxtData(void)501 void TrelDnssd::Peer::ReadExtAddrFromTxtData(void)
502 {
503     std::vector<Mdns::Publisher::TxtEntry> txtEntries;
504 
505     memset(&mExtAddr, 0, sizeof(mExtAddr));
506 
507     SuccessOrExit(Mdns::Publisher::DecodeTxtData(txtEntries, mTxtData.data(), mTxtData.size()));
508 
509     for (const auto &txtEntry : txtEntries)
510     {
511         if (txtEntry.mIsBooleanAttribute)
512         {
513             continue;
514         }
515 
516         if (StringUtils::EqualCaseInsensitive(txtEntry.mKey, kTxtRecordExtAddressKey))
517         {
518             VerifyOrExit(txtEntry.mValue.size() == sizeof(mExtAddr));
519 
520             memcpy(mExtAddr.m8, txtEntry.mValue.data(), sizeof(mExtAddr));
521             mValid = true;
522             break;
523         }
524     }
525 
526 exit:
527 
528     if (!mValid)
529     {
530         otbrLogInfo("Failed to dissect ExtAddr from peer TXT data");
531     }
532 
533     return;
534 }
535 
536 } // namespace TrelDnssd
537 
538 } // namespace otbr
539 
540 #endif // OTBR_ENABLE_TREL
541