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