1 /*
2 * Copyright (c) 2017, 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 implements mDNS publisher based on avahi.
32 */
33
34 #define OTBR_LOG_TAG "MDNS"
35
36 #include "mdns/mdns_avahi.hpp"
37
38 #include <algorithm>
39
40 #include <avahi-client/client.h>
41 #include <avahi-common/alternative.h>
42 #include <avahi-common/error.h>
43 #include <avahi-common/malloc.h>
44 #include <avahi-common/timeval.h>
45 #include <errno.h>
46 #include <inttypes.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <sys/socket.h>
51
52 #include "common/code_utils.hpp"
53 #include "common/logging.hpp"
54 #include "common/time.hpp"
55
56 struct AvahiWatch
57 {
58 int mFd; ///< The file descriptor to watch.
59 AvahiWatchEvent mEvents; ///< The interested events.
60 int mHappened; ///< The events happened.
61 AvahiWatchCallback mCallback; ///< The function to be called when interested events happened on mFd.
62 void * mContext; ///< A pointer to application-specific context.
63 void * mPoller; ///< The poller created this watch.
64
65 /**
66 * The constructor to initialize an Avahi watch.
67 *
68 * @param[in] aFd The file descriptor to watch.
69 * @param[in] aEvents The events to watch.
70 * @param[in] aCallback The function to be called when events happend on this file descriptor.
71 * @param[in] aContext A pointer to application-specific context.
72 * @param[in] aPoller The AvahiPoller this watcher belongs to.
73 *
74 */
AvahiWatchAvahiWatch75 AvahiWatch(int aFd, AvahiWatchEvent aEvents, AvahiWatchCallback aCallback, void *aContext, void *aPoller)
76 : mFd(aFd)
77 , mEvents(aEvents)
78 , mCallback(aCallback)
79 , mContext(aContext)
80 , mPoller(aPoller)
81 {
82 }
83 };
84
85 /**
86 * This structure implements the AvahiTimeout.
87 *
88 */
89 struct AvahiTimeout
90 {
91 otbr::Timepoint mTimeout; ///< Absolute time when this timer timeout.
92 AvahiTimeoutCallback mCallback; ///< The function to be called when timeout.
93 void * mContext; ///< The pointer to application-specific context.
94 void * mPoller; ///< The poller created this timer.
95
96 /**
97 * The constructor to initialize an AvahiTimeout.
98 *
99 * @param[in] aTimeout A pointer to the time after which the callback should be called.
100 * @param[in] aCallback The function to be called after timeout.
101 * @param[in] aContext A pointer to application-specific context.
102 * @param[in] aPoller The AvahiPoller this timeout belongs to.
103 *
104 */
AvahiTimeoutAvahiTimeout105 AvahiTimeout(const struct timeval *aTimeout, AvahiTimeoutCallback aCallback, void *aContext, void *aPoller)
106 : mCallback(aCallback)
107 , mContext(aContext)
108 , mPoller(aPoller)
109 {
110 if (aTimeout)
111 {
112 mTimeout = otbr::Clock::now() + otbr::FromTimeval<otbr::Microseconds>(*aTimeout);
113 }
114 else
115 {
116 mTimeout = otbr::Timepoint::min();
117 }
118 }
119 };
120
121 namespace otbr {
122
123 namespace Mdns {
124
DnsErrorToOtbrError(int aAvahiError)125 static otbrError DnsErrorToOtbrError(int aAvahiError)
126 {
127 otbrError error;
128
129 switch (aAvahiError)
130 {
131 case AVAHI_OK:
132 case AVAHI_ERR_INVALID_ADDRESS:
133 error = OTBR_ERROR_NONE;
134 break;
135
136 case AVAHI_ERR_NOT_FOUND:
137 error = OTBR_ERROR_NOT_FOUND;
138 break;
139
140 case AVAHI_ERR_INVALID_ARGUMENT:
141 error = OTBR_ERROR_INVALID_ARGS;
142 break;
143
144 case AVAHI_ERR_COLLISION:
145 error = OTBR_ERROR_DUPLICATED;
146 break;
147
148 case AVAHI_ERR_DNS_NOTIMP:
149 case AVAHI_ERR_NOT_SUPPORTED:
150 error = OTBR_ERROR_NOT_IMPLEMENTED;
151 break;
152
153 default:
154 error = OTBR_ERROR_MDNS;
155 break;
156 }
157
158 return error;
159 }
160
161 class AvahiPoller : public MainloopProcessor
162 {
163 public:
164 AvahiPoller(void);
165
166 // Implementation of MainloopProcessor.
167
168 void Update(MainloopContext &aMainloop) override;
169 void Process(const MainloopContext &aMainloop) override;
170
GetAvahiPoll(void) const171 const AvahiPoll *GetAvahiPoll(void) const { return &mAvahiPoller; }
172
173 private:
174 typedef std::vector<AvahiWatch *> Watches;
175 typedef std::vector<AvahiTimeout *> Timers;
176
177 static AvahiWatch * WatchNew(const struct AvahiPoll *aPoller,
178 int aFd,
179 AvahiWatchEvent aEvent,
180 AvahiWatchCallback aCallback,
181 void * aContext);
182 AvahiWatch * WatchNew(int aFd, AvahiWatchEvent aEvent, AvahiWatchCallback aCallback, void *aContext);
183 static void WatchUpdate(AvahiWatch *aWatch, AvahiWatchEvent aEvent);
184 static AvahiWatchEvent WatchGetEvents(AvahiWatch *aWatch);
185 static void WatchFree(AvahiWatch *aWatch);
186 void WatchFree(AvahiWatch &aWatch);
187 static AvahiTimeout * TimeoutNew(const AvahiPoll * aPoller,
188 const struct timeval *aTimeout,
189 AvahiTimeoutCallback aCallback,
190 void * aContext);
191 AvahiTimeout * TimeoutNew(const struct timeval *aTimeout, AvahiTimeoutCallback aCallback, void *aContext);
192 static void TimeoutUpdate(AvahiTimeout *aTimer, const struct timeval *aTimeout);
193 static void TimeoutFree(AvahiTimeout *aTimer);
194 void TimeoutFree(AvahiTimeout &aTimer);
195
196 Watches mWatches;
197 Timers mTimers;
198 AvahiPoll mAvahiPoller;
199 };
200
AvahiPoller(void)201 AvahiPoller::AvahiPoller(void)
202 {
203 mAvahiPoller.userdata = this;
204 mAvahiPoller.watch_new = WatchNew;
205 mAvahiPoller.watch_update = WatchUpdate;
206 mAvahiPoller.watch_get_events = WatchGetEvents;
207 mAvahiPoller.watch_free = WatchFree;
208
209 mAvahiPoller.timeout_new = TimeoutNew;
210 mAvahiPoller.timeout_update = TimeoutUpdate;
211 mAvahiPoller.timeout_free = TimeoutFree;
212 }
213
WatchNew(const struct AvahiPoll * aPoller,int aFd,AvahiWatchEvent aEvent,AvahiWatchCallback aCallback,void * aContext)214 AvahiWatch *AvahiPoller::WatchNew(const struct AvahiPoll *aPoller,
215 int aFd,
216 AvahiWatchEvent aEvent,
217 AvahiWatchCallback aCallback,
218 void * aContext)
219 {
220 return reinterpret_cast<AvahiPoller *>(aPoller->userdata)->WatchNew(aFd, aEvent, aCallback, aContext);
221 }
222
WatchNew(int aFd,AvahiWatchEvent aEvent,AvahiWatchCallback aCallback,void * aContext)223 AvahiWatch *AvahiPoller::WatchNew(int aFd, AvahiWatchEvent aEvent, AvahiWatchCallback aCallback, void *aContext)
224 {
225 assert(aEvent && aCallback && aFd >= 0);
226
227 mWatches.push_back(new AvahiWatch(aFd, aEvent, aCallback, aContext, this));
228
229 return mWatches.back();
230 }
231
WatchUpdate(AvahiWatch * aWatch,AvahiWatchEvent aEvent)232 void AvahiPoller::WatchUpdate(AvahiWatch *aWatch, AvahiWatchEvent aEvent)
233 {
234 aWatch->mEvents = aEvent;
235 }
236
WatchGetEvents(AvahiWatch * aWatch)237 AvahiWatchEvent AvahiPoller::WatchGetEvents(AvahiWatch *aWatch)
238 {
239 return static_cast<AvahiWatchEvent>(aWatch->mHappened);
240 }
241
WatchFree(AvahiWatch * aWatch)242 void AvahiPoller::WatchFree(AvahiWatch *aWatch)
243 {
244 reinterpret_cast<AvahiPoller *>(aWatch->mPoller)->WatchFree(*aWatch);
245 }
246
WatchFree(AvahiWatch & aWatch)247 void AvahiPoller::WatchFree(AvahiWatch &aWatch)
248 {
249 for (Watches::iterator it = mWatches.begin(); it != mWatches.end(); ++it)
250 {
251 if (*it == &aWatch)
252 {
253 mWatches.erase(it);
254 delete &aWatch;
255 break;
256 }
257 }
258 }
259
TimeoutNew(const AvahiPoll * aPoller,const struct timeval * aTimeout,AvahiTimeoutCallback aCallback,void * aContext)260 AvahiTimeout *AvahiPoller::TimeoutNew(const AvahiPoll * aPoller,
261 const struct timeval *aTimeout,
262 AvahiTimeoutCallback aCallback,
263 void * aContext)
264 {
265 assert(aPoller && aCallback);
266 return static_cast<AvahiPoller *>(aPoller->userdata)->TimeoutNew(aTimeout, aCallback, aContext);
267 }
268
TimeoutNew(const struct timeval * aTimeout,AvahiTimeoutCallback aCallback,void * aContext)269 AvahiTimeout *AvahiPoller::TimeoutNew(const struct timeval *aTimeout, AvahiTimeoutCallback aCallback, void *aContext)
270 {
271 mTimers.push_back(new AvahiTimeout(aTimeout, aCallback, aContext, this));
272 return mTimers.back();
273 }
274
TimeoutUpdate(AvahiTimeout * aTimer,const struct timeval * aTimeout)275 void AvahiPoller::TimeoutUpdate(AvahiTimeout *aTimer, const struct timeval *aTimeout)
276 {
277 if (aTimeout == nullptr)
278 {
279 aTimer->mTimeout = Timepoint::min();
280 }
281 else
282 {
283 aTimer->mTimeout = Clock::now() + FromTimeval<Microseconds>(*aTimeout);
284 }
285 }
286
TimeoutFree(AvahiTimeout * aTimer)287 void AvahiPoller::TimeoutFree(AvahiTimeout *aTimer)
288 {
289 static_cast<AvahiPoller *>(aTimer->mPoller)->TimeoutFree(*aTimer);
290 }
291
TimeoutFree(AvahiTimeout & aTimer)292 void AvahiPoller::TimeoutFree(AvahiTimeout &aTimer)
293 {
294 for (Timers::iterator it = mTimers.begin(); it != mTimers.end(); ++it)
295 {
296 if (*it == &aTimer)
297 {
298 mTimers.erase(it);
299 delete &aTimer;
300 break;
301 }
302 }
303 }
304
Update(MainloopContext & aMainloop)305 void AvahiPoller::Update(MainloopContext &aMainloop)
306 {
307 Timepoint now = Clock::now();
308
309 for (Watches::iterator it = mWatches.begin(); it != mWatches.end(); ++it)
310 {
311 int fd = (*it)->mFd;
312 AvahiWatchEvent events = (*it)->mEvents;
313
314 if (AVAHI_WATCH_IN & events)
315 {
316 FD_SET(fd, &aMainloop.mReadFdSet);
317 }
318
319 if (AVAHI_WATCH_OUT & events)
320 {
321 FD_SET(fd, &aMainloop.mWriteFdSet);
322 }
323
324 if (AVAHI_WATCH_ERR & events)
325 {
326 FD_SET(fd, &aMainloop.mErrorFdSet);
327 }
328
329 if (AVAHI_WATCH_HUP & events)
330 {
331 // TODO what do with this event type?
332 }
333
334 aMainloop.mMaxFd = std::max(aMainloop.mMaxFd, fd);
335
336 (*it)->mHappened = 0;
337 }
338
339 for (Timers::iterator it = mTimers.begin(); it != mTimers.end(); ++it)
340 {
341 Timepoint timeout = (*it)->mTimeout;
342
343 if (timeout == Timepoint::min())
344 {
345 continue;
346 }
347
348 if (timeout <= now)
349 {
350 aMainloop.mTimeout = ToTimeval(Microseconds::zero());
351 break;
352 }
353 else
354 {
355 auto delay = std::chrono::duration_cast<Microseconds>(timeout - now);
356
357 if (delay < FromTimeval<Microseconds>(aMainloop.mTimeout))
358 {
359 aMainloop.mTimeout = ToTimeval(delay);
360 }
361 }
362 }
363 }
364
Process(const MainloopContext & aMainloop)365 void AvahiPoller::Process(const MainloopContext &aMainloop)
366 {
367 Timepoint now = Clock::now();
368 std::vector<AvahiTimeout *> expired;
369
370 for (Watches::iterator it = mWatches.begin(); it != mWatches.end(); ++it)
371 {
372 int fd = (*it)->mFd;
373 AvahiWatchEvent events = (*it)->mEvents;
374
375 (*it)->mHappened = 0;
376
377 if ((AVAHI_WATCH_IN & events) && FD_ISSET(fd, &aMainloop.mReadFdSet))
378 {
379 (*it)->mHappened |= AVAHI_WATCH_IN;
380 }
381
382 if ((AVAHI_WATCH_OUT & events) && FD_ISSET(fd, &aMainloop.mWriteFdSet))
383 {
384 (*it)->mHappened |= AVAHI_WATCH_OUT;
385 }
386
387 if ((AVAHI_WATCH_ERR & events) && FD_ISSET(fd, &aMainloop.mErrorFdSet))
388 {
389 (*it)->mHappened |= AVAHI_WATCH_ERR;
390 }
391
392 // TODO hup events
393 if ((*it)->mHappened)
394 {
395 (*it)->mCallback(*it, (*it)->mFd, static_cast<AvahiWatchEvent>((*it)->mHappened), (*it)->mContext);
396 }
397 }
398
399 for (Timers::iterator it = mTimers.begin(); it != mTimers.end(); ++it)
400 {
401 if ((*it)->mTimeout == Timepoint::min())
402 {
403 continue;
404 }
405
406 if ((*it)->mTimeout <= now)
407 {
408 expired.push_back(*it);
409 }
410 }
411
412 for (std::vector<AvahiTimeout *>::iterator it = expired.begin(); it != expired.end(); ++it)
413 {
414 AvahiTimeout *avahiTimeout = *it;
415
416 avahiTimeout->mCallback(avahiTimeout, avahiTimeout->mContext);
417 }
418 }
419
PublisherAvahi(StateCallback aStateCallback)420 PublisherAvahi::PublisherAvahi(StateCallback aStateCallback)
421 : mClient(nullptr)
422 , mPoller(MakeUnique<AvahiPoller>())
423 , mState(State::kIdle)
424 , mStateCallback(std::move(aStateCallback))
425 {
426 }
427
~PublisherAvahi(void)428 PublisherAvahi::~PublisherAvahi(void)
429 {
430 Stop();
431 }
432
~AvahiServiceRegistration(void)433 PublisherAvahi::AvahiServiceRegistration::~AvahiServiceRegistration(void)
434 {
435 ReleaseGroup(mEntryGroup);
436 }
437
~AvahiHostRegistration(void)438 PublisherAvahi::AvahiHostRegistration::~AvahiHostRegistration(void)
439 {
440 ReleaseGroup(mEntryGroup);
441 }
442
Start(void)443 otbrError PublisherAvahi::Start(void)
444 {
445 otbrError error = OTBR_ERROR_NONE;
446 int avahiError = AVAHI_OK;
447
448 assert(mClient == nullptr);
449
450 mClient = avahi_client_new(mPoller->GetAvahiPoll(), AVAHI_CLIENT_NO_FAIL, HandleClientState, this, &avahiError);
451
452 if (avahiError != AVAHI_OK)
453 {
454 otbrLogErr("Failed to create avahi client: %s!", avahi_strerror(avahiError));
455 error = OTBR_ERROR_MDNS;
456 }
457
458 return error;
459 }
460
IsStarted(void) const461 bool PublisherAvahi::IsStarted(void) const
462 {
463 return mClient != nullptr;
464 }
465
Stop(void)466 void PublisherAvahi::Stop(void)
467 {
468 mServiceRegistrations.clear();
469 mHostRegistrations.clear();
470
471 mSubscribedServices.clear();
472 mSubscribedHosts.clear();
473
474 if (mClient)
475 {
476 avahi_client_free(mClient);
477 mClient = nullptr;
478 }
479
480 mState = Mdns::Publisher::State::kIdle;
481 }
482
HandleClientState(AvahiClient * aClient,AvahiClientState aState,void * aContext)483 void PublisherAvahi::HandleClientState(AvahiClient *aClient, AvahiClientState aState, void *aContext)
484 {
485 static_cast<PublisherAvahi *>(aContext)->HandleClientState(aClient, aState);
486 }
487
HandleGroupState(AvahiEntryGroup * aGroup,AvahiEntryGroupState aState,void * aContext)488 void PublisherAvahi::HandleGroupState(AvahiEntryGroup *aGroup, AvahiEntryGroupState aState, void *aContext)
489 {
490 static_cast<PublisherAvahi *>(aContext)->HandleGroupState(aGroup, aState);
491 }
492
HandleGroupState(AvahiEntryGroup * aGroup,AvahiEntryGroupState aState)493 void PublisherAvahi::HandleGroupState(AvahiEntryGroup *aGroup, AvahiEntryGroupState aState)
494 {
495 switch (aState)
496 {
497 case AVAHI_ENTRY_GROUP_ESTABLISHED:
498 otbrLogInfo("Avahi group (@%p) is established", aGroup);
499 CallHostOrServiceCallback(aGroup, OTBR_ERROR_NONE);
500 break;
501
502 case AVAHI_ENTRY_GROUP_COLLISION:
503 otbrLogInfo("Avahi group (@%p) name conflicted", aGroup);
504 CallHostOrServiceCallback(aGroup, OTBR_ERROR_DUPLICATED);
505 break;
506
507 case AVAHI_ENTRY_GROUP_FAILURE:
508 otbrLogErr("Avahi group (@%p) failed: %s!", aGroup,
509 avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(aGroup))));
510 CallHostOrServiceCallback(aGroup, OTBR_ERROR_MDNS);
511 break;
512
513 case AVAHI_ENTRY_GROUP_UNCOMMITED:
514 case AVAHI_ENTRY_GROUP_REGISTERING:
515 break;
516
517 default:
518 assert(false);
519 break;
520 }
521 }
522
CallHostOrServiceCallback(AvahiEntryGroup * aGroup,otbrError aError)523 void PublisherAvahi::CallHostOrServiceCallback(AvahiEntryGroup *aGroup, otbrError aError)
524 {
525 ServiceRegistration *serviceReg;
526 HostRegistration * hostReg;
527
528 if ((serviceReg = FindServiceRegistration(aGroup)) != nullptr)
529 {
530 if (aError == OTBR_ERROR_NONE)
531 {
532 serviceReg->Complete(aError);
533 }
534 else
535 {
536 RemoveServiceRegistration(serviceReg->mName, serviceReg->mType, aError);
537 }
538 }
539 else if ((hostReg = FindHostRegistration(aGroup)) != nullptr)
540 {
541 if (aError == OTBR_ERROR_NONE)
542 {
543 hostReg->Complete(aError);
544 }
545 else
546 {
547 RemoveHostRegistration(hostReg->mName, aError);
548 }
549 }
550 else
551 {
552 otbrLogWarning("No registered service or host matches avahi group @%p", aGroup);
553 }
554 }
555
CreateGroup(AvahiClient * aClient)556 AvahiEntryGroup *PublisherAvahi::CreateGroup(AvahiClient *aClient)
557 {
558 AvahiEntryGroup *group = avahi_entry_group_new(aClient, HandleGroupState, this);
559
560 if (group == nullptr)
561 {
562 otbrLogErr("Failed to create entry avahi group: %s", avahi_strerror(avahi_client_errno(aClient)));
563 }
564
565 return group;
566 }
567
ReleaseGroup(AvahiEntryGroup * aGroup)568 void PublisherAvahi::ReleaseGroup(AvahiEntryGroup *aGroup)
569 {
570 int error;
571
572 otbrLogInfo("Releasing avahi entry group @%p", aGroup);
573
574 error = avahi_entry_group_reset(aGroup);
575
576 if (error != 0)
577 {
578 otbrLogErr("Failed to reset entry group for avahi error: %s", avahi_strerror(error));
579 }
580
581 error = avahi_entry_group_free(aGroup);
582 if (error != 0)
583 {
584 otbrLogErr("Failed to free entry group for avahi error: %s", avahi_strerror(error));
585 }
586 }
587
HandleClientState(AvahiClient * aClient,AvahiClientState aState)588 void PublisherAvahi::HandleClientState(AvahiClient *aClient, AvahiClientState aState)
589 {
590 otbrLogInfo("Avahi client state changed to %d", aState);
591
592 switch (aState)
593 {
594 case AVAHI_CLIENT_S_RUNNING:
595 // The server has startup successfully and registered its host
596 // name on the network, so it's time to create our services.
597 otbrLogInfo("Avahi client is ready");
598 mClient = aClient;
599 mState = State::kReady;
600 mStateCallback(mState);
601 break;
602
603 case AVAHI_CLIENT_FAILURE:
604 otbrLogErr("Avahi client failed to start: %s", avahi_strerror(avahi_client_errno(aClient)));
605 mState = State::kIdle;
606 mStateCallback(mState);
607 Stop();
608 Start();
609 break;
610
611 case AVAHI_CLIENT_S_COLLISION:
612 // Let's drop our registered services. When the server is back
613 // in AVAHI_SERVER_RUNNING state we will register them again
614 // with the new host name.
615 otbrLogErr("Avahi client collision detected: %s", avahi_strerror(avahi_client_errno(aClient)));
616
617 // fall through
618
619 case AVAHI_CLIENT_S_REGISTERING:
620 // The server records are now being established. This might be
621 // caused by a host name change. We need to wait for our own
622 // records to register until the host name is properly established.
623 mServiceRegistrations.clear();
624 mHostRegistrations.clear();
625 break;
626
627 case AVAHI_CLIENT_CONNECTING:
628 otbrLogInfo("Avahi client is connecting to the server");
629 break;
630
631 default:
632 assert(false);
633 break;
634 }
635 }
636
PublishServiceImpl(const std::string & aHostName,const std::string & aName,const std::string & aType,const SubTypeList & aSubTypeList,uint16_t aPort,const TxtList & aTxtList,ResultCallback && aCallback)637 void PublisherAvahi::PublishServiceImpl(const std::string &aHostName,
638 const std::string &aName,
639 const std::string &aType,
640 const SubTypeList &aSubTypeList,
641 uint16_t aPort,
642 const TxtList & aTxtList,
643 ResultCallback && aCallback)
644 {
645 otbrError error = OTBR_ERROR_NONE;
646 int avahiError = AVAHI_OK;
647 SubTypeList sortedSubTypeList = SortSubTypeList(aSubTypeList);
648 TxtList sortedTxtList = SortTxtList(aTxtList);
649 const std::string logHostName = !aHostName.empty() ? aHostName : "localhost";
650 std::string fullHostName;
651 AvahiEntryGroup * group = nullptr;
652
653 // Aligned with AvahiStringList
654 AvahiStringList txtBuffer[(kMaxSizeOfTxtRecord - 1) / sizeof(AvahiStringList) + 1];
655 AvahiStringList *txtHead = nullptr;
656
657 VerifyOrExit(mState == State::kReady, error = OTBR_ERROR_INVALID_STATE);
658 VerifyOrExit(mClient != nullptr, error = OTBR_ERROR_INVALID_STATE);
659
660 if (!aHostName.empty())
661 {
662 fullHostName = MakeFullHostName(aHostName);
663 }
664
665 aCallback = HandleDuplicateServiceRegistration(aHostName, aName, aType, sortedSubTypeList, aPort, sortedTxtList,
666 std::move(aCallback));
667 VerifyOrExit(!aCallback.IsNull());
668
669 SuccessOrExit(error = TxtListToAvahiStringList(aTxtList, txtBuffer, sizeof(txtBuffer), txtHead));
670 VerifyOrExit((group = CreateGroup(mClient)) != nullptr, error = OTBR_ERROR_MDNS);
671 avahiError = avahi_entry_group_add_service_strlst(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AvahiPublishFlags{},
672 aName.c_str(), aType.c_str(),
673 /* domain */ nullptr, fullHostName.c_str(), aPort, txtHead);
674 VerifyOrExit(avahiError == AVAHI_OK);
675
676 for (const std::string &subType : aSubTypeList)
677 {
678 otbrLogInfo("Add subtype %s for service %s.%s", subType.c_str(), aName.c_str(), aType.c_str());
679 std::string fullSubType = subType + "._sub." + aType;
680 avahiError = avahi_entry_group_add_service_subtype(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
681 AvahiPublishFlags{}, aName.c_str(), aType.c_str(),
682 /* domain */ nullptr, fullSubType.c_str());
683 VerifyOrExit(avahiError == AVAHI_OK);
684 }
685
686 otbrLogInfo("Commit avahi service %s.%s", aName.c_str(), aType.c_str());
687 avahiError = avahi_entry_group_commit(group);
688 VerifyOrExit(avahiError == AVAHI_OK);
689
690 AddServiceRegistration(std::unique_ptr<AvahiServiceRegistration>(new AvahiServiceRegistration(
691 aHostName, aName, aType, sortedSubTypeList, aPort, sortedTxtList, std::move(aCallback), group, this)));
692
693 exit:
694 if (avahiError != AVAHI_OK || error != OTBR_ERROR_NONE)
695 {
696 if (avahiError != AVAHI_OK)
697 {
698 error = OTBR_ERROR_MDNS;
699 otbrLogErr("Failed to publish service for avahi error: %s!", avahi_strerror(avahiError));
700 }
701
702 if (group != nullptr)
703 {
704 ReleaseGroup(group);
705 }
706 std::move(aCallback)(error);
707 }
708 }
709
UnpublishService(const std::string & aName,const std::string & aType,ResultCallback && aCallback)710 void PublisherAvahi::UnpublishService(const std::string &aName, const std::string &aType, ResultCallback &&aCallback)
711 {
712 otbrError error = OTBR_ERROR_NONE;
713
714 VerifyOrExit(mState == Publisher::State::kReady, error = OTBR_ERROR_INVALID_STATE);
715 RemoveServiceRegistration(aName, aType, OTBR_ERROR_ABORTED);
716
717 exit:
718 std::move(aCallback)(error);
719 }
720
PublishHostImpl(const std::string & aName,const std::vector<uint8_t> & aAddress,ResultCallback && aCallback)721 void PublisherAvahi::PublishHostImpl(const std::string & aName,
722 const std::vector<uint8_t> &aAddress,
723 ResultCallback && aCallback)
724 {
725 otbrError error = OTBR_ERROR_NONE;
726 int avahiError = AVAHI_OK;
727 std::string fullHostName;
728 AvahiAddress address;
729 AvahiEntryGroup *group = nullptr;
730
731 VerifyOrExit(mState == State::kReady, error = OTBR_ERROR_INVALID_STATE);
732 VerifyOrExit(mClient != nullptr, error = OTBR_ERROR_INVALID_STATE);
733 VerifyOrExit(aAddress.size() == sizeof(address.data.ipv6.address), error = OTBR_ERROR_INVALID_ARGS);
734
735 aCallback = HandleDuplicateHostRegistration(aName, aAddress, std::move(aCallback));
736 VerifyOrExit(!aCallback.IsNull());
737
738 address.proto = AVAHI_PROTO_INET6;
739 memcpy(address.data.ipv6.address, aAddress.data(), aAddress.size());
740 fullHostName = MakeFullHostName(aName);
741
742 VerifyOrExit((group = CreateGroup(mClient)) != nullptr, error = OTBR_ERROR_MDNS);
743 avahiError = avahi_entry_group_add_address(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AVAHI_PUBLISH_NO_REVERSE,
744 fullHostName.c_str(), &address);
745 VerifyOrExit(avahiError == AVAHI_OK);
746
747 otbrLogInfo("Commit avahi host %s", aName.c_str());
748 avahiError = avahi_entry_group_commit(group);
749 VerifyOrExit(avahiError == AVAHI_OK);
750
751 AddHostRegistration(std::unique_ptr<AvahiHostRegistration>(
752 new AvahiHostRegistration(aName, aAddress, std::move(aCallback), group, this)));
753
754 exit:
755 if (avahiError != AVAHI_OK || error != OTBR_ERROR_NONE)
756 {
757 if (avahiError != AVAHI_OK)
758 {
759 error = OTBR_ERROR_MDNS;
760 otbrLogErr("Failed to publish host for avahi error: %s!", avahi_strerror(avahiError));
761 }
762
763 if (group != nullptr)
764 {
765 ReleaseGroup(group);
766 }
767 std::move(aCallback)(error);
768 }
769 }
770
UnpublishHost(const std::string & aName,ResultCallback && aCallback)771 void PublisherAvahi::UnpublishHost(const std::string &aName, ResultCallback &&aCallback)
772 {
773 otbrError error = OTBR_ERROR_NONE;
774
775 VerifyOrExit(mState == Publisher::State::kReady, error = OTBR_ERROR_INVALID_STATE);
776 RemoveHostRegistration(aName, OTBR_ERROR_ABORTED);
777
778 exit:
779 std::move(aCallback)(error);
780 }
781
TxtListToAvahiStringList(const TxtList & aTxtList,AvahiStringList * aBuffer,size_t aBufferSize,AvahiStringList * & aHead)782 otbrError PublisherAvahi::TxtListToAvahiStringList(const TxtList & aTxtList,
783 AvahiStringList * aBuffer,
784 size_t aBufferSize,
785 AvahiStringList *&aHead)
786 {
787 otbrError error = OTBR_ERROR_NONE;
788 size_t used = 0;
789 AvahiStringList *last = nullptr;
790 AvahiStringList *curr = aBuffer;
791
792 aHead = nullptr;
793 for (const auto &txtEntry : aTxtList)
794 {
795 const char * name = txtEntry.mName.c_str();
796 size_t nameLength = txtEntry.mName.length();
797 const uint8_t *value = txtEntry.mValue.data();
798 size_t valueLength = txtEntry.mValue.size();
799 // +1 for the size of "=", avahi doesn't need '\0' at the end of the entry
800 size_t needed = sizeof(AvahiStringList) - sizeof(AvahiStringList::text) + nameLength + valueLength + 1;
801
802 VerifyOrExit(used + needed <= aBufferSize, error = OTBR_ERROR_INVALID_ARGS);
803 curr->next = last;
804 last = curr;
805 memcpy(curr->text, name, nameLength);
806 curr->text[nameLength] = '=';
807 memcpy(curr->text + nameLength + 1, value, valueLength);
808 curr->size = nameLength + valueLength + 1;
809 {
810 const uint8_t *next = curr->text + curr->size;
811 curr = OTBR_ALIGNED(next, AvahiStringList *);
812 }
813 used = static_cast<size_t>(reinterpret_cast<uint8_t *>(curr) - reinterpret_cast<uint8_t *>(aBuffer));
814 }
815 SuccessOrExit(error);
816 aHead = last;
817 exit:
818 return error;
819 }
820
FindServiceRegistration(const AvahiEntryGroup * aEntryGroup)821 Publisher::ServiceRegistration *PublisherAvahi::FindServiceRegistration(const AvahiEntryGroup *aEntryGroup)
822 {
823 ServiceRegistration *result = nullptr;
824
825 for (const auto &kv : mServiceRegistrations)
826 {
827 const auto &serviceReg = static_cast<const AvahiServiceRegistration &>(*kv.second);
828 if (serviceReg.GetEntryGroup() == aEntryGroup)
829 {
830 result = kv.second.get();
831 break;
832 }
833 }
834
835 return result;
836 }
837
FindHostRegistration(const AvahiEntryGroup * aEntryGroup)838 Publisher::HostRegistration *PublisherAvahi::FindHostRegistration(const AvahiEntryGroup *aEntryGroup)
839 {
840 HostRegistration *result = nullptr;
841
842 for (const auto &kv : mHostRegistrations)
843 {
844 const auto &hostReg = static_cast<const AvahiHostRegistration &>(*kv.second);
845 if (hostReg.GetEntryGroup() == aEntryGroup)
846 {
847 result = kv.second.get();
848 break;
849 }
850 }
851
852 return result;
853 }
854
SubscribeService(const std::string & aType,const std::string & aInstanceName)855 void PublisherAvahi::SubscribeService(const std::string &aType, const std::string &aInstanceName)
856 {
857 auto service = MakeUnique<ServiceSubscription>(*this, aType, aInstanceName);
858
859 VerifyOrExit(mState == Publisher::State::kReady);
860 mSubscribedServices.push_back(std::move(service));
861
862 otbrLogInfo("Subscribe service %s.%s (total %zu)", aInstanceName.c_str(), aType.c_str(),
863 mSubscribedServices.size());
864
865 if (aInstanceName.empty())
866 {
867 mSubscribedServices.back()->Browse();
868 }
869 else
870 {
871 mSubscribedServices.back()->Resolve(AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, aInstanceName, aType);
872 }
873
874 exit:
875 return;
876 }
877
UnsubscribeService(const std::string & aType,const std::string & aInstanceName)878 void PublisherAvahi::UnsubscribeService(const std::string &aType, const std::string &aInstanceName)
879 {
880 ServiceSubscriptionList::iterator it;
881
882 VerifyOrExit(mState == Publisher::State::kReady);
883 it = std::find_if(mSubscribedServices.begin(), mSubscribedServices.end(),
884 [&aType, &aInstanceName](const std::unique_ptr<ServiceSubscription> &aService) {
885 return aService->mType == aType && aService->mInstanceName == aInstanceName;
886 });
887
888 assert(it != mSubscribedServices.end());
889
890 {
891 std::unique_ptr<ServiceSubscription> service = std::move(*it);
892
893 mSubscribedServices.erase(it);
894 service->Release();
895 }
896
897 otbrLogInfo("Unsubscribe service %s.%s (left %zu)", aInstanceName.c_str(), aType.c_str(),
898 mSubscribedServices.size());
899
900 exit:
901 return;
902 }
903
OnServiceResolveFailedImpl(const std::string & aType,const std::string & aInstanceName,int32_t aErrorCode)904 void PublisherAvahi::OnServiceResolveFailedImpl(const std::string &aType,
905 const std::string &aInstanceName,
906 int32_t aErrorCode)
907 {
908 otbrLogWarning("Resolve service %s.%s failed: %s", aInstanceName.c_str(), aType.c_str(),
909 avahi_strerror(aErrorCode));
910 }
911
OnHostResolveFailedImpl(const std::string & aHostName,int32_t aErrorCode)912 void PublisherAvahi::OnHostResolveFailedImpl(const std::string &aHostName, int32_t aErrorCode)
913 {
914 otbrLogWarning("Resolve host %s failed: %s", aHostName.c_str(), avahi_strerror(aErrorCode));
915 }
916
DnsErrorToOtbrError(int32_t aErrorCode)917 otbrError PublisherAvahi::DnsErrorToOtbrError(int32_t aErrorCode)
918 {
919 return otbr::Mdns::DnsErrorToOtbrError(aErrorCode);
920 }
921
SubscribeHost(const std::string & aHostName)922 void PublisherAvahi::SubscribeHost(const std::string &aHostName)
923 {
924 auto host = MakeUnique<HostSubscription>(*this, aHostName);
925
926 VerifyOrExit(mState == Publisher::State::kReady);
927
928 mSubscribedHosts.push_back(std::move(host));
929
930 otbrLogInfo("Subscribe host %s (total %zu)", aHostName.c_str(), mSubscribedHosts.size());
931
932 mSubscribedHosts.back()->Resolve();
933
934 exit:
935 return;
936 }
937
UnsubscribeHost(const std::string & aHostName)938 void PublisherAvahi::UnsubscribeHost(const std::string &aHostName)
939 {
940 HostSubscriptionList::iterator it;
941
942 VerifyOrExit(mState == Publisher::State::kReady);
943 it = std::find_if(
944 mSubscribedHosts.begin(), mSubscribedHosts.end(),
945 [&aHostName](const std::unique_ptr<HostSubscription> &aHost) { return aHost->mHostName == aHostName; });
946
947 assert(it != mSubscribedHosts.end());
948
949 {
950 std::unique_ptr<HostSubscription> host = std::move(*it);
951
952 mSubscribedHosts.erase(it);
953 host->Release();
954 }
955
956 otbrLogInfo("Unsubscribe host %s (remaining %zu)", aHostName.c_str(), mSubscribedHosts.size());
957
958 exit:
959 return;
960 }
961
Create(StateCallback aStateCallback)962 Publisher *Publisher::Create(StateCallback aStateCallback)
963 {
964 return new PublisherAvahi(std::move(aStateCallback));
965 }
966
Destroy(Publisher * aPublisher)967 void Publisher::Destroy(Publisher *aPublisher)
968 {
969 delete static_cast<PublisherAvahi *>(aPublisher);
970 }
971
Browse(void)972 void PublisherAvahi::ServiceSubscription::Browse(void)
973 {
974 assert(mPublisherAvahi->mClient != nullptr);
975
976 otbrLogInfo("Browse service %s", mType.c_str());
977 mServiceBrowser =
978 avahi_service_browser_new(mPublisherAvahi->mClient, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, mType.c_str(),
979 /* domain */ nullptr, static_cast<AvahiLookupFlags>(0), HandleBrowseResult, this);
980 if (!mServiceBrowser)
981 {
982 otbrLogWarning("Failed to browse service %s: %s", mType.c_str(),
983 avahi_strerror(avahi_client_errno(mPublisherAvahi->mClient)));
984 }
985 }
986
Release(void)987 void PublisherAvahi::ServiceSubscription::Release(void)
988 {
989 for (AvahiServiceResolver *resolver : mServiceResolvers)
990 {
991 avahi_service_resolver_free(resolver);
992 }
993
994 mServiceResolvers.clear();
995
996 if (mServiceBrowser != nullptr)
997 {
998 avahi_service_browser_free(mServiceBrowser);
999 mServiceBrowser = nullptr;
1000 }
1001 }
1002
HandleBrowseResult(AvahiServiceBrowser * aServiceBrowser,AvahiIfIndex aInterfaceIndex,AvahiProtocol aProtocol,AvahiBrowserEvent aEvent,const char * aName,const char * aType,const char * aDomain,AvahiLookupResultFlags aFlags,void * aContext)1003 void PublisherAvahi::ServiceSubscription::HandleBrowseResult(AvahiServiceBrowser * aServiceBrowser,
1004 AvahiIfIndex aInterfaceIndex,
1005 AvahiProtocol aProtocol,
1006 AvahiBrowserEvent aEvent,
1007 const char * aName,
1008 const char * aType,
1009 const char * aDomain,
1010 AvahiLookupResultFlags aFlags,
1011 void * aContext)
1012 {
1013 static_cast<PublisherAvahi::ServiceSubscription *>(aContext)->HandleBrowseResult(
1014 aServiceBrowser, aInterfaceIndex, aProtocol, aEvent, aName, aType, aDomain, aFlags);
1015 }
1016
HandleBrowseResult(AvahiServiceBrowser * aServiceBrowser,AvahiIfIndex aInterfaceIndex,AvahiProtocol aProtocol,AvahiBrowserEvent aEvent,const char * aName,const char * aType,const char * aDomain,AvahiLookupResultFlags aFlags)1017 void PublisherAvahi::ServiceSubscription::HandleBrowseResult(AvahiServiceBrowser * aServiceBrowser,
1018 AvahiIfIndex aInterfaceIndex,
1019 AvahiProtocol aProtocol,
1020 AvahiBrowserEvent aEvent,
1021 const char * aName,
1022 const char * aType,
1023 const char * aDomain,
1024 AvahiLookupResultFlags aFlags)
1025 {
1026 OTBR_UNUSED_VARIABLE(aServiceBrowser);
1027 OTBR_UNUSED_VARIABLE(aProtocol);
1028 OTBR_UNUSED_VARIABLE(aDomain);
1029
1030 assert(mServiceBrowser == aServiceBrowser);
1031
1032 otbrLogInfo("Browse service reply: %s.%s proto %d inf %u event %d flags %d", aName, aType, aProtocol,
1033 aInterfaceIndex, static_cast<int>(aEvent), static_cast<int>(aFlags));
1034
1035 switch (aEvent)
1036 {
1037 case AVAHI_BROWSER_NEW:
1038 Resolve(aInterfaceIndex, aProtocol, aName, aType);
1039 break;
1040 case AVAHI_BROWSER_REMOVE:
1041 mPublisherAvahi->OnServiceRemoved(static_cast<uint32_t>(aInterfaceIndex), aType, aName);
1042 break;
1043 case AVAHI_BROWSER_CACHE_EXHAUSTED:
1044 case AVAHI_BROWSER_ALL_FOR_NOW:
1045 // do nothing
1046 break;
1047 case AVAHI_BROWSER_FAILURE:
1048 mPublisherAvahi->OnServiceResolveFailed(aType, aName, avahi_client_errno(mPublisherAvahi->mClient));
1049 break;
1050 }
1051 }
1052
Resolve(uint32_t aInterfaceIndex,AvahiProtocol aProtocol,const std::string & aInstanceName,const std::string & aType)1053 void PublisherAvahi::ServiceSubscription::Resolve(uint32_t aInterfaceIndex,
1054 AvahiProtocol aProtocol,
1055 const std::string &aInstanceName,
1056 const std::string &aType)
1057 {
1058 AvahiServiceResolver *resolver;
1059
1060 mPublisherAvahi->mServiceInstanceResolutionBeginTime[std::make_pair(aInstanceName, aType)] = Clock::now();
1061
1062 otbrLogInfo("Resolve service %s.%s inf %" PRIu32, aInstanceName.c_str(), aType.c_str(), aInterfaceIndex);
1063
1064 resolver = avahi_service_resolver_new(
1065 mPublisherAvahi->mClient, aInterfaceIndex, aProtocol, aInstanceName.c_str(), aType.c_str(),
1066 /* domain */ nullptr, AVAHI_PROTO_INET6, static_cast<AvahiLookupFlags>(0), HandleResolveResult, this);
1067 if (resolver != nullptr)
1068 {
1069 AddServiceResolver(resolver);
1070 }
1071 else
1072 {
1073 otbrLogErr("Failed to resolve serivce %s: %s", mType.c_str(),
1074 avahi_strerror(avahi_client_errno(mPublisherAvahi->mClient)));
1075 }
1076 }
1077
HandleResolveResult(AvahiServiceResolver * aServiceResolver,AvahiIfIndex aInterfaceIndex,AvahiProtocol aProtocol,AvahiResolverEvent aEvent,const char * aName,const char * aType,const char * aDomain,const char * aHostName,const AvahiAddress * aAddress,uint16_t aPort,AvahiStringList * aTxt,AvahiLookupResultFlags aFlags,void * aContext)1078 void PublisherAvahi::ServiceSubscription::HandleResolveResult(AvahiServiceResolver * aServiceResolver,
1079 AvahiIfIndex aInterfaceIndex,
1080 AvahiProtocol aProtocol,
1081 AvahiResolverEvent aEvent,
1082 const char * aName,
1083 const char * aType,
1084 const char * aDomain,
1085 const char * aHostName,
1086 const AvahiAddress * aAddress,
1087 uint16_t aPort,
1088 AvahiStringList * aTxt,
1089 AvahiLookupResultFlags aFlags,
1090 void * aContext)
1091 {
1092 static_cast<PublisherAvahi::ServiceSubscription *>(aContext)->HandleResolveResult(
1093 aServiceResolver, aInterfaceIndex, aProtocol, aEvent, aName, aType, aDomain, aHostName, aAddress, aPort, aTxt,
1094 aFlags);
1095 }
1096
HandleResolveResult(AvahiServiceResolver * aServiceResolver,AvahiIfIndex aInterfaceIndex,AvahiProtocol aProtocol,AvahiResolverEvent aEvent,const char * aName,const char * aType,const char * aDomain,const char * aHostName,const AvahiAddress * aAddress,uint16_t aPort,AvahiStringList * aTxt,AvahiLookupResultFlags aFlags)1097 void PublisherAvahi::ServiceSubscription::HandleResolveResult(AvahiServiceResolver * aServiceResolver,
1098 AvahiIfIndex aInterfaceIndex,
1099 AvahiProtocol aProtocol,
1100 AvahiResolverEvent aEvent,
1101 const char * aName,
1102 const char * aType,
1103 const char * aDomain,
1104 const char * aHostName,
1105 const AvahiAddress * aAddress,
1106 uint16_t aPort,
1107 AvahiStringList * aTxt,
1108 AvahiLookupResultFlags aFlags)
1109 {
1110 OT_UNUSED_VARIABLE(aServiceResolver);
1111 OT_UNUSED_VARIABLE(aInterfaceIndex);
1112 OT_UNUSED_VARIABLE(aProtocol);
1113 OT_UNUSED_VARIABLE(aType);
1114 OT_UNUSED_VARIABLE(aDomain);
1115
1116 char addrBuf[AVAHI_ADDRESS_STR_MAX] = "";
1117 Ip6Address address;
1118 size_t totalTxtSize = 0;
1119 DiscoveredInstanceInfo instanceInfo;
1120 bool resolved = false;
1121 int avahiError = AVAHI_OK;
1122
1123 otbrLog(aEvent == AVAHI_RESOLVER_FOUND ? OTBR_LOG_INFO : OTBR_LOG_WARNING, OTBR_LOG_TAG,
1124 "Resolve service reply: protocol %d %s.%s.%s = host %s port %" PRIu16 " flags %d event %d", aProtocol,
1125 aName, aType, aDomain, aHostName, aPort, static_cast<int>(aFlags), static_cast<int>(aEvent));
1126
1127 VerifyOrExit(aEvent == AVAHI_RESOLVER_FOUND, avahiError = avahi_client_errno(mPublisherAvahi->mClient));
1128 avahi_address_snprint(addrBuf, sizeof(addrBuf), aAddress);
1129 otbrLogInfo("Resolve service reply: address %s", addrBuf);
1130
1131 RemoveServiceResolver(aServiceResolver);
1132 VerifyOrExit(aHostName != nullptr, avahiError = AVAHI_ERR_INVALID_HOST_NAME);
1133
1134 instanceInfo.mNetifIndex = static_cast<uint32_t>(aInterfaceIndex);
1135 instanceInfo.mName = aName;
1136 instanceInfo.mHostName = std::string(aHostName) + ".";
1137 instanceInfo.mPort = aPort;
1138 VerifyOrExit(otbrError::OTBR_ERROR_NONE == Ip6Address::FromString(addrBuf, address),
1139 otbrLogErr("Failed to parse the IP address: %s", addrBuf), avahiError = AVAHI_ERR_INVALID_ADDRESS);
1140
1141 otbrLogInfo("Resolve service reply: flags=%u, host=%s", aFlags, aHostName);
1142
1143 VerifyOrExit(!address.IsLinkLocal() && !address.IsMulticast() && !address.IsLoopback() && !address.IsUnspecified(),
1144 otbrLogInfo("Ignoring address %s", address.ToString().c_str()),
1145 avahiError = AVAHI_ERR_INVALID_ADDRESS);
1146
1147 instanceInfo.mAddresses.push_back(address);
1148
1149 // TODO priority
1150 // TODO weight
1151 // TODO use a more proper TTL
1152 instanceInfo.mTtl = kDefaultTtl;
1153 for (auto p = aTxt; p; p = avahi_string_list_get_next(p))
1154 {
1155 totalTxtSize += avahi_string_list_get_size(p) + 1;
1156 }
1157 instanceInfo.mTxtData.resize(totalTxtSize);
1158 avahi_string_list_serialize(aTxt, instanceInfo.mTxtData.data(), totalTxtSize);
1159
1160 otbrLogInfo("Resolve service reply: address=%s, ttl=%" PRIu32, address.ToString().c_str(), instanceInfo.mTtl);
1161
1162 resolved = true;
1163
1164 exit:
1165 if (resolved)
1166 {
1167 // NOTE: This `ServiceSubscrption` object may be freed in `OnServiceResolved`.
1168 mPublisherAvahi->OnServiceResolved(mType, instanceInfo);
1169 }
1170 else if (avahiError != AVAHI_OK)
1171 {
1172 mPublisherAvahi->OnServiceResolveFailed(aType, aName, avahiError);
1173 }
1174 }
1175
AddServiceResolver(AvahiServiceResolver * aServiceResolver)1176 void PublisherAvahi::ServiceSubscription::AddServiceResolver(AvahiServiceResolver *aServiceResolver)
1177 {
1178 assert(aServiceResolver != nullptr);
1179 mServiceResolvers.insert(aServiceResolver);
1180 }
1181
RemoveServiceResolver(AvahiServiceResolver * aServiceResolver)1182 void PublisherAvahi::ServiceSubscription::RemoveServiceResolver(AvahiServiceResolver *aServiceResolver)
1183 {
1184 assert(aServiceResolver != nullptr);
1185 assert(mServiceResolvers.find(aServiceResolver) != mServiceResolvers.end());
1186
1187 avahi_service_resolver_free(aServiceResolver);
1188 mServiceResolvers.erase(aServiceResolver);
1189 }
1190
Release(void)1191 void PublisherAvahi::HostSubscription::Release(void)
1192 {
1193 if (mRecordBrowser != nullptr)
1194 {
1195 avahi_record_browser_free(mRecordBrowser);
1196 mRecordBrowser = nullptr;
1197 }
1198 }
1199
Resolve(void)1200 void PublisherAvahi::HostSubscription::Resolve(void)
1201 {
1202 std::string fullHostName = MakeFullHostName(mHostName);
1203
1204 mPublisherAvahi->mHostResolutionBeginTime[mHostName] = Clock::now();
1205
1206 otbrLogInfo("Resolve host %s inf %d", fullHostName.c_str(), static_cast<int>(AVAHI_IF_UNSPEC));
1207 mRecordBrowser = avahi_record_browser_new(mPublisherAvahi->mClient, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
1208 fullHostName.c_str(), AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_AAAA,
1209 static_cast<AvahiLookupFlags>(0), HandleResolveResult, this);
1210 if (!mRecordBrowser)
1211 {
1212 otbrLogErr("Failed to resolve host %s: %s", fullHostName.c_str(),
1213 avahi_strerror(avahi_client_errno(mPublisherAvahi->mClient)));
1214 }
1215 }
1216
HandleResolveResult(AvahiRecordBrowser * aRecordBrowser,AvahiIfIndex aInterfaceIndex,AvahiProtocol aProtocol,AvahiBrowserEvent aEvent,const char * aName,uint16_t aClazz,uint16_t aType,const void * aRdata,size_t aSize,AvahiLookupResultFlags aFlags,void * aContext)1217 void PublisherAvahi::HostSubscription::HandleResolveResult(AvahiRecordBrowser * aRecordBrowser,
1218 AvahiIfIndex aInterfaceIndex,
1219 AvahiProtocol aProtocol,
1220 AvahiBrowserEvent aEvent,
1221 const char * aName,
1222 uint16_t aClazz,
1223 uint16_t aType,
1224 const void * aRdata,
1225 size_t aSize,
1226 AvahiLookupResultFlags aFlags,
1227 void * aContext)
1228 {
1229 static_cast<PublisherAvahi::HostSubscription *>(aContext)->HandleResolveResult(
1230 aRecordBrowser, aInterfaceIndex, aProtocol, aEvent, aName, aClazz, aType, aRdata, aSize, aFlags);
1231 }
1232
HandleResolveResult(AvahiRecordBrowser * aRecordBrowser,AvahiIfIndex aInterfaceIndex,AvahiProtocol aProtocol,AvahiBrowserEvent aEvent,const char * aName,uint16_t aClazz,uint16_t aType,const void * aRdata,size_t aSize,AvahiLookupResultFlags aFlags)1233 void PublisherAvahi::HostSubscription::HandleResolveResult(AvahiRecordBrowser * aRecordBrowser,
1234 AvahiIfIndex aInterfaceIndex,
1235 AvahiProtocol aProtocol,
1236 AvahiBrowserEvent aEvent,
1237 const char * aName,
1238 uint16_t aClazz,
1239 uint16_t aType,
1240 const void * aRdata,
1241 size_t aSize,
1242 AvahiLookupResultFlags aFlags)
1243 {
1244 OTBR_UNUSED_VARIABLE(aRecordBrowser);
1245 OTBR_UNUSED_VARIABLE(aInterfaceIndex);
1246 OTBR_UNUSED_VARIABLE(aProtocol);
1247 OTBR_UNUSED_VARIABLE(aEvent);
1248 OTBR_UNUSED_VARIABLE(aClazz);
1249 OTBR_UNUSED_VARIABLE(aType);
1250 OTBR_UNUSED_VARIABLE(aFlags);
1251
1252 Ip6Address address;
1253 bool resolved = false;
1254 int avahiError = AVAHI_OK;
1255
1256 otbrLog(aEvent != AVAHI_BROWSER_FAILURE ? OTBR_LOG_INFO : OTBR_LOG_WARNING, OTBR_LOG_TAG,
1257 "Resolve host reply: %s inf %d protocol %d class %" PRIu16 " type %" PRIu16 " size %zu flags %d event %d",
1258 aName, aInterfaceIndex, aProtocol, aClazz, aType, aSize, static_cast<int>(aFlags),
1259 static_cast<int>(aEvent));
1260
1261 VerifyOrExit(aEvent == AVAHI_BROWSER_NEW);
1262 VerifyOrExit(aSize == OTBR_IP6_ADDRESS_SIZE || aSize == OTBR_IP4_ADDRESS_SIZE,
1263 otbrLogErr("Unexpected address data length: %zu", aSize), avahiError = AVAHI_ERR_INVALID_ADDRESS);
1264 VerifyOrExit(aSize == OTBR_IP6_ADDRESS_SIZE, otbrLogInfo("IPv4 address ignored"),
1265 avahiError = AVAHI_ERR_INVALID_ADDRESS);
1266 address = Ip6Address(*static_cast<const uint8_t(*)[OTBR_IP6_ADDRESS_SIZE]>(aRdata));
1267
1268 VerifyOrExit(!address.IsLinkLocal() && !address.IsMulticast() && !address.IsLoopback() && !address.IsUnspecified(),
1269 avahiError = AVAHI_ERR_INVALID_ADDRESS);
1270 otbrLogInfo("Resolved host address: %s", address.ToString().c_str());
1271
1272 mHostInfo.mHostName = std::string(aName) + ".";
1273 mHostInfo.mAddresses.push_back(std::move(address));
1274 // TODO: Use a more proper TTL
1275 mHostInfo.mTtl = kDefaultTtl;
1276 resolved = true;
1277
1278 exit:
1279 if (resolved)
1280 {
1281 // NOTE: This `HostSubscrption` object may be freed in `OnHostResolved`.
1282 mPublisherAvahi->OnHostResolved(mHostName, mHostInfo);
1283 }
1284 else if (avahiError != AVAHI_OK)
1285 {
1286 mPublisherAvahi->OnHostResolveFailed(mHostName, avahiError);
1287 }
1288 }
1289
1290 } // namespace Mdns
1291
1292 } // namespace otbr
1293