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