• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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::ControllerOpenThread & aNcp,Mdns::Publisher & aPublisher)84 TrelDnssd::TrelDnssd(Ncp::ControllerOpenThread &aNcp, Mdns::Publisher &aPublisher)
85     : mPublisher(aPublisher)
86     , mNcp(aNcp)
87 {
88     sTrelDnssd = this;
89 }
90 
Initialize(std::string aTrelNetif)91 void TrelDnssd::Initialize(std::string aTrelNetif)
92 {
93     mTrelNetif = std::move(aTrelNetif);
94 
95     if (IsInitialized())
96     {
97         otbrLogDebug("Initialized on netif \"%s\"", mTrelNetif.c_str());
98         CheckTrelNetifReady();
99     }
100     else
101     {
102         otbrLogDebug("Not initialized");
103     }
104 }
105 
StartBrowse(void)106 void TrelDnssd::StartBrowse(void)
107 {
108     VerifyOrExit(IsInitialized());
109 
110     otbrLogDebug("Start browsing %s services ...", kTrelServiceName);
111 
112     assert(mSubscriberId == 0);
113     mSubscriberId = mPublisher.AddSubscriptionCallbacks(
114         [this](const std::string &aType, const Mdns::Publisher::DiscoveredInstanceInfo &aInstanceInfo) {
115             OnTrelServiceInstanceResolved(aType, aInstanceInfo);
116         },
117         /* aHostCallback */ nullptr);
118 
119     if (IsReady())
120     {
121         mPublisher.SubscribeService(kTrelServiceName, /* aInstanceName */ "");
122     }
123 
124 exit:
125     return;
126 }
127 
StopBrowse(void)128 void TrelDnssd::StopBrowse(void)
129 {
130     VerifyOrExit(IsInitialized());
131 
132     otbrLogDebug("Stop browsing %s service.", kTrelServiceName);
133     assert(mSubscriberId > 0);
134 
135     mPublisher.RemoveSubscriptionCallbacks(mSubscriberId);
136     mSubscriberId = 0;
137 
138     if (IsReady())
139     {
140         mPublisher.UnsubscribeService(kTrelServiceName, "");
141     }
142 
143 exit:
144     return;
145 }
146 
RegisterService(uint16_t aPort,const uint8_t * aTxtData,uint8_t aTxtLength)147 void TrelDnssd::RegisterService(uint16_t aPort, const uint8_t *aTxtData, uint8_t aTxtLength)
148 {
149     assert(aPort > 0);
150     assert(aTxtData != nullptr);
151 
152     VerifyOrExit(IsInitialized());
153 
154     otbrLogDebug("Register %s service: port=%u, TXT=%d bytes", kTrelServiceName, aPort, aTxtLength);
155     otbrDump(OTBR_LOG_DEBUG, OTBR_LOG_TAG, "TXT", aTxtData, aTxtLength);
156 
157     if (mRegisterInfo.IsValid() && IsReady())
158     {
159         UnpublishTrelService();
160     }
161 
162     mRegisterInfo.Assign(aPort, aTxtData, aTxtLength);
163 
164     if (IsReady())
165     {
166         PublishTrelService();
167     }
168 
169 exit:
170     return;
171 }
172 
UnregisterService(void)173 void TrelDnssd::UnregisterService(void)
174 {
175     // Return if service has not been registered
176     VerifyOrExit(IsInitialized() && mRegisterInfo.IsValid());
177 
178     otbrLogDebug("Remove %s service", kTrelServiceName);
179 
180     if (IsReady())
181     {
182         UnpublishTrelService();
183     }
184 
185     mRegisterInfo.Clear();
186 
187 exit:
188     return;
189 }
190 
HandleMdnsState(Mdns::Publisher::State aState)191 void TrelDnssd::HandleMdnsState(Mdns::Publisher::State aState)
192 {
193     VerifyOrExit(IsInitialized());
194     VerifyOrExit(aState == Mdns::Publisher::State::kReady);
195 
196     otbrLogDebug("mDNS Publisher is Ready");
197     mMdnsPublisherReady = true;
198     RemoveAllPeers();
199 
200     if (mRegisterInfo.IsPublished())
201     {
202         mRegisterInfo.mInstanceName = "";
203     }
204 
205     OnBecomeReady();
206 
207 exit:
208     return;
209 }
210 
OnTrelServiceInstanceResolved(const std::string & aType,const Mdns::Publisher::DiscoveredInstanceInfo & aInstanceInfo)211 void TrelDnssd::OnTrelServiceInstanceResolved(const std::string                             &aType,
212                                               const Mdns::Publisher::DiscoveredInstanceInfo &aInstanceInfo)
213 {
214     VerifyOrExit(StringUtils::EqualCaseInsensitive(aType, kTrelServiceName));
215     VerifyOrExit(aInstanceInfo.mNetifIndex == mTrelNetifIndex);
216 
217     if (aInstanceInfo.mRemoved)
218     {
219         OnTrelServiceInstanceRemoved(aInstanceInfo.mName);
220     }
221     else
222     {
223         OnTrelServiceInstanceAdded(aInstanceInfo);
224     }
225 
226 exit:
227     return;
228 }
229 
GetTrelInstanceName(void)230 std::string TrelDnssd::GetTrelInstanceName(void)
231 {
232     const otExtAddress *extaddr = otLinkGetExtendedAddress(mNcp.GetInstance());
233     std::string         name;
234     char                nameBuf[sizeof(otExtAddress) * 2 + 1];
235 
236     Utils::Bytes2Hex(extaddr->m8, sizeof(otExtAddress), nameBuf);
237     name = StringUtils::ToLowercase(nameBuf);
238 
239     assert(name.length() == sizeof(otExtAddress) * 2);
240 
241     otbrLogDebug("Using instance name %s", name.c_str());
242     return name;
243 }
244 
PublishTrelService(void)245 void TrelDnssd::PublishTrelService(void)
246 {
247     assert(mRegisterInfo.IsValid());
248     assert(!mRegisterInfo.IsPublished());
249     assert(mTrelNetifIndex > 0);
250 
251     mRegisterInfo.mInstanceName = GetTrelInstanceName();
252     mPublisher.PublishService(/* aHostName */ "", mRegisterInfo.mInstanceName, kTrelServiceName,
253                               Mdns::Publisher::SubTypeList{}, mRegisterInfo.mPort, mRegisterInfo.mTxtData,
254                               [](otbrError aError) { HandlePublishTrelServiceError(aError); });
255 }
256 
HandlePublishTrelServiceError(otbrError aError)257 void TrelDnssd::HandlePublishTrelServiceError(otbrError aError)
258 {
259     if (aError != OTBR_ERROR_NONE)
260     {
261         otbrLogErr("Failed to publish TREL service: %s. TREL won't be working.", otbrErrorString(aError));
262     }
263 }
264 
UnpublishTrelService(void)265 void TrelDnssd::UnpublishTrelService(void)
266 {
267     assert(mRegisterInfo.IsValid());
268     assert(mRegisterInfo.IsPublished());
269 
270     mPublisher.UnpublishService(mRegisterInfo.mInstanceName, kTrelServiceName,
271                                 [](otbrError aError) { HandleUnpublishTrelServiceError(aError); });
272     mRegisterInfo.mInstanceName = "";
273 }
274 
HandleUnpublishTrelServiceError(otbrError aError)275 void TrelDnssd::HandleUnpublishTrelServiceError(otbrError aError)
276 {
277     if (aError != OTBR_ERROR_NONE)
278     {
279         otbrLogInfo("Failed to unpublish TREL service: %s", otbrErrorString(aError));
280     }
281 }
282 
OnTrelServiceInstanceAdded(const Mdns::Publisher::DiscoveredInstanceInfo & aInstanceInfo)283 void TrelDnssd::OnTrelServiceInstanceAdded(const Mdns::Publisher::DiscoveredInstanceInfo &aInstanceInfo)
284 {
285     std::string        instanceName = StringUtils::ToLowercase(aInstanceInfo.mName);
286     Ip6Address         selectedAddress;
287     otPlatTrelPeerInfo peerInfo;
288 
289     // Remove any existing TREL service instance before adding
290     OnTrelServiceInstanceRemoved(instanceName);
291 
292     otbrLogDebug("Peer discovered: %s hostname %s addresses %zu port %d priority %d "
293                  "weight %d",
294                  aInstanceInfo.mName.c_str(), aInstanceInfo.mHostName.c_str(), aInstanceInfo.mAddresses.size(),
295                  aInstanceInfo.mPort, aInstanceInfo.mPriority, aInstanceInfo.mWeight);
296 
297     for (const auto &addr : aInstanceInfo.mAddresses)
298     {
299         otbrLogDebug("Peer address: %s", addr.ToString().c_str());
300 
301         // Skip anycast (Refer to https://datatracker.ietf.org/doc/html/rfc2373#section-2.6.1)
302         if (addr.m64[1] == 0)
303         {
304             continue;
305         }
306 
307         // If there are multiple addresses, we prefer the address
308         // which is numerically smallest. This prefers GUA over ULA
309         // (`fc00::/7`) and then link-local (`fe80::/10`).
310 
311         if (selectedAddress.IsUnspecified() || (addr < selectedAddress))
312         {
313             selectedAddress = addr;
314         }
315     }
316 
317     if (aInstanceInfo.mAddresses.empty())
318     {
319         otbrLogWarning("Peer %s does not have any IPv6 address, ignored", aInstanceInfo.mName.c_str());
320         ExitNow();
321     }
322 
323     peerInfo.mRemoved = false;
324     memcpy(&peerInfo.mSockAddr.mAddress, &selectedAddress, sizeof(peerInfo.mSockAddr.mAddress));
325     peerInfo.mSockAddr.mPort = aInstanceInfo.mPort;
326     peerInfo.mTxtData        = aInstanceInfo.mTxtData.data();
327     peerInfo.mTxtLength      = aInstanceInfo.mTxtData.size();
328 
329     {
330         Peer peer(aInstanceInfo.mTxtData, peerInfo.mSockAddr);
331 
332         VerifyOrExit(peer.mValid, otbrLogWarning("Peer %s is invalid", aInstanceInfo.mName.c_str()));
333 
334         otPlatTrelHandleDiscoveredPeerInfo(mNcp.GetInstance(), &peerInfo);
335 
336         mPeers.emplace(instanceName, peer);
337         CheckPeersNumLimit();
338     }
339 
340 exit:
341     return;
342 }
343 
OnTrelServiceInstanceRemoved(const std::string & aInstanceName)344 void TrelDnssd::OnTrelServiceInstanceRemoved(const std::string &aInstanceName)
345 {
346     std::string instanceName = StringUtils::ToLowercase(aInstanceName);
347     auto        it           = mPeers.find(instanceName);
348 
349     VerifyOrExit(it != mPeers.end());
350 
351     otbrLogDebug("Peer removed: %s", instanceName.c_str());
352 
353     // Remove the peer only when all instances are removed because one peer can have multiple instances if expired
354     // instances were not properly removed by mDNS.
355     if (CountDuplicatePeers(it->second) == 0)
356     {
357         NotifyRemovePeer(it->second);
358     }
359 
360     mPeers.erase(it);
361 
362 exit:
363     return;
364 }
365 
CheckPeersNumLimit(void)366 void TrelDnssd::CheckPeersNumLimit(void)
367 {
368     const PeerMap::value_type *oldestPeer = nullptr;
369 
370     VerifyOrExit(mPeers.size() >= kPeerCacheSize);
371 
372     for (const auto &entry : mPeers)
373     {
374         if (oldestPeer == nullptr || entry.second.mDiscoverTime < oldestPeer->second.mDiscoverTime)
375         {
376             oldestPeer = &entry;
377         }
378     }
379 
380     OnTrelServiceInstanceRemoved(oldestPeer->first);
381 
382 exit:
383     return;
384 }
385 
NotifyRemovePeer(const Peer & aPeer)386 void TrelDnssd::NotifyRemovePeer(const Peer &aPeer)
387 {
388     otPlatTrelPeerInfo peerInfo;
389 
390     peerInfo.mRemoved   = true;
391     peerInfo.mTxtData   = aPeer.mTxtData.data();
392     peerInfo.mTxtLength = aPeer.mTxtData.size();
393     peerInfo.mSockAddr  = aPeer.mSockAddr;
394 
395     otPlatTrelHandleDiscoveredPeerInfo(mNcp.GetInstance(), &peerInfo);
396 }
397 
RemoveAllPeers(void)398 void TrelDnssd::RemoveAllPeers(void)
399 {
400     for (const auto &entry : mPeers)
401     {
402         NotifyRemovePeer(entry.second);
403     }
404 
405     mPeers.clear();
406 }
407 
CheckTrelNetifReady(void)408 void TrelDnssd::CheckTrelNetifReady(void)
409 {
410     assert(IsInitialized());
411 
412     if (mTrelNetifIndex == 0)
413     {
414         mTrelNetifIndex = if_nametoindex(mTrelNetif.c_str());
415 
416         if (mTrelNetifIndex != 0)
417         {
418             otbrLogDebug("Netif %s is ready: index = %" PRIu32, mTrelNetif.c_str(), mTrelNetifIndex);
419             OnBecomeReady();
420         }
421         else
422         {
423             uint16_t delay = kCheckNetifReadyIntervalMs;
424 
425             otbrLogWarning("Netif %s is not ready (%s), will retry after %d seconds", mTrelNetif.c_str(),
426                            strerror(errno), delay / 1000);
427             mTaskRunner.Post(Milliseconds(delay), [this]() { CheckTrelNetifReady(); });
428         }
429     }
430 }
431 
IsReady(void) const432 bool TrelDnssd::IsReady(void) const
433 {
434     assert(IsInitialized());
435 
436     return mTrelNetifIndex > 0 && mMdnsPublisherReady;
437 }
438 
OnBecomeReady(void)439 void TrelDnssd::OnBecomeReady(void)
440 {
441     if (IsReady())
442     {
443         otbrLogInfo("TREL DNS-SD Is Now Ready: Netif=%s(%" PRIu32 "), SubscriberId=%" PRIu64 ", Register=%s!",
444                     mTrelNetif.c_str(), mTrelNetifIndex, mSubscriberId, mRegisterInfo.mInstanceName.c_str());
445 
446         if (mSubscriberId > 0)
447         {
448             mPublisher.SubscribeService(kTrelServiceName, /* aInstanceName */ "");
449         }
450 
451         if (mRegisterInfo.IsValid())
452         {
453             PublishTrelService();
454         }
455     }
456 }
457 
CountDuplicatePeers(const TrelDnssd::Peer & aPeer)458 uint16_t TrelDnssd::CountDuplicatePeers(const TrelDnssd::Peer &aPeer)
459 {
460     uint16_t count = 0;
461 
462     for (const auto &entry : mPeers)
463     {
464         if (&entry.second == &aPeer)
465         {
466             continue;
467         }
468 
469         if (!memcmp(&entry.second.mSockAddr, &aPeer.mSockAddr, sizeof(otSockAddr)) &&
470             !memcmp(&entry.second.mExtAddr, &aPeer.mExtAddr, sizeof(otExtAddress)))
471         {
472             count++;
473         }
474     }
475 
476     return count;
477 }
478 
Assign(uint16_t aPort,const uint8_t * aTxtData,uint8_t aTxtLength)479 void TrelDnssd::RegisterInfo::Assign(uint16_t aPort, const uint8_t *aTxtData, uint8_t aTxtLength)
480 {
481     assert(!IsPublished());
482     assert(aPort > 0);
483 
484     mPort = aPort;
485     mTxtData.assign(aTxtData, aTxtData + aTxtLength);
486 }
487 
Clear(void)488 void TrelDnssd::RegisterInfo::Clear(void)
489 {
490     assert(!IsPublished());
491 
492     mPort = 0;
493     mTxtData.clear();
494 }
495 
496 const char TrelDnssd::Peer::kTxtRecordExtAddressKey[] = "xa";
497 
ReadExtAddrFromTxtData(void)498 void TrelDnssd::Peer::ReadExtAddrFromTxtData(void)
499 {
500     std::vector<Mdns::Publisher::TxtEntry> txtEntries;
501 
502     memset(&mExtAddr, 0, sizeof(mExtAddr));
503 
504     SuccessOrExit(Mdns::Publisher::DecodeTxtData(txtEntries, mTxtData.data(), mTxtData.size()));
505 
506     for (const auto &txtEntry : txtEntries)
507     {
508         if (txtEntry.mIsBooleanAttribute)
509         {
510             continue;
511         }
512 
513         if (StringUtils::EqualCaseInsensitive(txtEntry.mKey, kTxtRecordExtAddressKey))
514         {
515             VerifyOrExit(txtEntry.mValue.size() == sizeof(mExtAddr));
516 
517             memcpy(mExtAddr.m8, txtEntry.mValue.data(), sizeof(mExtAddr));
518             mValid = true;
519             break;
520         }
521     }
522 
523 exit:
524 
525     if (!mValid)
526     {
527         otbrLogInfo("Failed to dissect ExtAddr from peer TXT data");
528     }
529 
530     return;
531 }
532 
533 } // namespace TrelDnssd
534 
535 } // namespace otbr
536 
537 #endif // OTBR_ENABLE_TREL
538