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 namespace otbr {
57 namespace Mdns {
58
59 class AvahiPoller;
60
61 } // namespace Mdns
62 } // namespace otbr
63
64 struct AvahiWatch
65 {
66 typedef otbr::Mdns::AvahiPoller AvahiPoller;
67
68 int mFd; ///< The file descriptor to watch.
69 AvahiWatchEvent mEvents; ///< The interested events.
70 int mHappened; ///< The events happened.
71 AvahiWatchCallback mCallback; ///< The function to be called to report events happened on `mFd`.
72 void *mContext; ///< A pointer to application-specific context to use with `mCallback`.
73 bool mShouldReport; ///< Whether or not we need to report events (invoking callback).
74 AvahiPoller &mPoller; ///< The poller owning this watch.
75
76 /**
77 * The constructor to initialize an Avahi watch.
78 *
79 * @param[in] aFd The file descriptor to watch.
80 * @param[in] aEvents The events to watch.
81 * @param[in] aCallback The function to be called when events happened on this file descriptor.
82 * @param[in] aContext A pointer to application-specific context.
83 * @param[in] aPoller The AvahiPoller this watcher belongs to.
84 *
85 */
AvahiWatchAvahiWatch86 AvahiWatch(int aFd, AvahiWatchEvent aEvents, AvahiWatchCallback aCallback, void *aContext, AvahiPoller &aPoller)
87 : mFd(aFd)
88 , mEvents(aEvents)
89 , mCallback(aCallback)
90 , mContext(aContext)
91 , mShouldReport(false)
92 , mPoller(aPoller)
93 {
94 }
95 };
96
97 /**
98 * This structure implements the AvahiTimeout.
99 *
100 */
101 struct AvahiTimeout
102 {
103 typedef otbr::Mdns::AvahiPoller AvahiPoller;
104
105 otbr::Timepoint mTimeout; ///< Absolute time when this timer timeout.
106 AvahiTimeoutCallback mCallback; ///< The function to be called when timeout.
107 void *mContext; ///< The pointer to application-specific context.
108 bool mShouldReport; ///< Whether or not timeout occurred and need to reported (invoking callback).
109 AvahiPoller &mPoller; ///< The poller created this timer.
110
111 /**
112 * The constructor to initialize an AvahiTimeout.
113 *
114 * @param[in] aTimeout A pointer to the time after which the callback should be called.
115 * @param[in] aCallback The function to be called after timeout.
116 * @param[in] aContext A pointer to application-specific context.
117 * @param[in] aPoller The AvahiPoller this timeout belongs to.
118 *
119 */
AvahiTimeoutAvahiTimeout120 AvahiTimeout(const struct timeval *aTimeout, AvahiTimeoutCallback aCallback, void *aContext, AvahiPoller &aPoller)
121 : mCallback(aCallback)
122 , mContext(aContext)
123 , mShouldReport(false)
124 , mPoller(aPoller)
125 {
126 if (aTimeout)
127 {
128 mTimeout = otbr::Clock::now() + otbr::FromTimeval<otbr::Microseconds>(*aTimeout);
129 }
130 else
131 {
132 mTimeout = otbr::Timepoint::min();
133 }
134 }
135 };
136
137 namespace otbr {
138
139 namespace Mdns {
140
DnsErrorToOtbrError(int aAvahiError)141 static otbrError DnsErrorToOtbrError(int aAvahiError)
142 {
143 otbrError error;
144
145 switch (aAvahiError)
146 {
147 case AVAHI_OK:
148 case AVAHI_ERR_INVALID_ADDRESS:
149 error = OTBR_ERROR_NONE;
150 break;
151
152 case AVAHI_ERR_NOT_FOUND:
153 error = OTBR_ERROR_NOT_FOUND;
154 break;
155
156 case AVAHI_ERR_INVALID_ARGUMENT:
157 error = OTBR_ERROR_INVALID_ARGS;
158 break;
159
160 case AVAHI_ERR_COLLISION:
161 error = OTBR_ERROR_DUPLICATED;
162 break;
163
164 case AVAHI_ERR_DNS_NOTIMP:
165 case AVAHI_ERR_NOT_SUPPORTED:
166 error = OTBR_ERROR_NOT_IMPLEMENTED;
167 break;
168
169 default:
170 error = OTBR_ERROR_MDNS;
171 break;
172 }
173
174 return error;
175 }
176
177 class AvahiPoller : public MainloopProcessor
178 {
179 public:
180 AvahiPoller(void);
181
182 // Implementation of MainloopProcessor.
183
184 void Update(MainloopContext &aMainloop) override;
185 void Process(const MainloopContext &aMainloop) override;
186
GetAvahiPoll(void) const187 const AvahiPoll *GetAvahiPoll(void) const { return &mAvahiPoll; }
188
189 private:
190 typedef std::vector<AvahiWatch *> Watches;
191 typedef std::vector<AvahiTimeout *> Timers;
192
193 static AvahiWatch *WatchNew(const struct AvahiPoll *aPoll,
194 int aFd,
195 AvahiWatchEvent aEvent,
196 AvahiWatchCallback aCallback,
197 void *aContext);
198 AvahiWatch *WatchNew(int aFd, AvahiWatchEvent aEvent, AvahiWatchCallback aCallback, void *aContext);
199 static void WatchUpdate(AvahiWatch *aWatch, AvahiWatchEvent aEvent);
200 static AvahiWatchEvent WatchGetEvents(AvahiWatch *aWatch);
201 static void WatchFree(AvahiWatch *aWatch);
202 void WatchFree(AvahiWatch &aWatch);
203 static AvahiTimeout *TimeoutNew(const AvahiPoll *aPoll,
204 const struct timeval *aTimeout,
205 AvahiTimeoutCallback aCallback,
206 void *aContext);
207 AvahiTimeout *TimeoutNew(const struct timeval *aTimeout, AvahiTimeoutCallback aCallback, void *aContext);
208 static void TimeoutUpdate(AvahiTimeout *aTimer, const struct timeval *aTimeout);
209 static void TimeoutFree(AvahiTimeout *aTimer);
210 void TimeoutFree(AvahiTimeout &aTimer);
211
212 Watches mWatches;
213 Timers mTimers;
214 AvahiPoll mAvahiPoll;
215 };
216
AvahiPoller(void)217 AvahiPoller::AvahiPoller(void)
218 {
219 mAvahiPoll.userdata = this;
220 mAvahiPoll.watch_new = WatchNew;
221 mAvahiPoll.watch_update = WatchUpdate;
222 mAvahiPoll.watch_get_events = WatchGetEvents;
223 mAvahiPoll.watch_free = WatchFree;
224
225 mAvahiPoll.timeout_new = TimeoutNew;
226 mAvahiPoll.timeout_update = TimeoutUpdate;
227 mAvahiPoll.timeout_free = TimeoutFree;
228 }
229
WatchNew(const struct AvahiPoll * aPoll,int aFd,AvahiWatchEvent aEvent,AvahiWatchCallback aCallback,void * aContext)230 AvahiWatch *AvahiPoller::WatchNew(const struct AvahiPoll *aPoll,
231 int aFd,
232 AvahiWatchEvent aEvent,
233 AvahiWatchCallback aCallback,
234 void *aContext)
235 {
236 return reinterpret_cast<AvahiPoller *>(aPoll->userdata)->WatchNew(aFd, aEvent, aCallback, aContext);
237 }
238
WatchNew(int aFd,AvahiWatchEvent aEvent,AvahiWatchCallback aCallback,void * aContext)239 AvahiWatch *AvahiPoller::WatchNew(int aFd, AvahiWatchEvent aEvent, AvahiWatchCallback aCallback, void *aContext)
240 {
241 assert(aEvent && aCallback && aFd >= 0);
242
243 mWatches.push_back(new AvahiWatch(aFd, aEvent, aCallback, aContext, *this));
244
245 return mWatches.back();
246 }
247
WatchUpdate(AvahiWatch * aWatch,AvahiWatchEvent aEvent)248 void AvahiPoller::WatchUpdate(AvahiWatch *aWatch, AvahiWatchEvent aEvent)
249 {
250 aWatch->mEvents = aEvent;
251 }
252
WatchGetEvents(AvahiWatch * aWatch)253 AvahiWatchEvent AvahiPoller::WatchGetEvents(AvahiWatch *aWatch)
254 {
255 return static_cast<AvahiWatchEvent>(aWatch->mHappened);
256 }
257
WatchFree(AvahiWatch * aWatch)258 void AvahiPoller::WatchFree(AvahiWatch *aWatch)
259 {
260 aWatch->mPoller.WatchFree(*aWatch);
261 }
262
WatchFree(AvahiWatch & aWatch)263 void AvahiPoller::WatchFree(AvahiWatch &aWatch)
264 {
265 for (Watches::iterator it = mWatches.begin(); it != mWatches.end(); ++it)
266 {
267 if (*it == &aWatch)
268 {
269 mWatches.erase(it);
270 delete &aWatch;
271 break;
272 }
273 }
274 }
275
TimeoutNew(const AvahiPoll * aPoll,const struct timeval * aTimeout,AvahiTimeoutCallback aCallback,void * aContext)276 AvahiTimeout *AvahiPoller::TimeoutNew(const AvahiPoll *aPoll,
277 const struct timeval *aTimeout,
278 AvahiTimeoutCallback aCallback,
279 void *aContext)
280 {
281 assert(aPoll && aCallback);
282 return static_cast<AvahiPoller *>(aPoll->userdata)->TimeoutNew(aTimeout, aCallback, aContext);
283 }
284
TimeoutNew(const struct timeval * aTimeout,AvahiTimeoutCallback aCallback,void * aContext)285 AvahiTimeout *AvahiPoller::TimeoutNew(const struct timeval *aTimeout, AvahiTimeoutCallback aCallback, void *aContext)
286 {
287 mTimers.push_back(new AvahiTimeout(aTimeout, aCallback, aContext, *this));
288 return mTimers.back();
289 }
290
TimeoutUpdate(AvahiTimeout * aTimer,const struct timeval * aTimeout)291 void AvahiPoller::TimeoutUpdate(AvahiTimeout *aTimer, const struct timeval *aTimeout)
292 {
293 if (aTimeout == nullptr)
294 {
295 aTimer->mTimeout = Timepoint::min();
296 }
297 else
298 {
299 aTimer->mTimeout = Clock::now() + FromTimeval<Microseconds>(*aTimeout);
300 }
301 }
302
TimeoutFree(AvahiTimeout * aTimer)303 void AvahiPoller::TimeoutFree(AvahiTimeout *aTimer)
304 {
305 aTimer->mPoller.TimeoutFree(*aTimer);
306 }
307
TimeoutFree(AvahiTimeout & aTimer)308 void AvahiPoller::TimeoutFree(AvahiTimeout &aTimer)
309 {
310 for (Timers::iterator it = mTimers.begin(); it != mTimers.end(); ++it)
311 {
312 if (*it == &aTimer)
313 {
314 mTimers.erase(it);
315 delete &aTimer;
316 break;
317 }
318 }
319 }
320
Update(MainloopContext & aMainloop)321 void AvahiPoller::Update(MainloopContext &aMainloop)
322 {
323 Timepoint now = Clock::now();
324
325 for (AvahiWatch *watch : mWatches)
326 {
327 int fd = watch->mFd;
328 AvahiWatchEvent events = watch->mEvents;
329
330 if (AVAHI_WATCH_IN & events)
331 {
332 FD_SET(fd, &aMainloop.mReadFdSet);
333 }
334
335 if (AVAHI_WATCH_OUT & events)
336 {
337 FD_SET(fd, &aMainloop.mWriteFdSet);
338 }
339
340 if (AVAHI_WATCH_ERR & events)
341 {
342 FD_SET(fd, &aMainloop.mErrorFdSet);
343 }
344
345 if (AVAHI_WATCH_HUP & events)
346 {
347 // TODO what do with this event type?
348 }
349
350 aMainloop.mMaxFd = std::max(aMainloop.mMaxFd, fd);
351
352 watch->mHappened = 0;
353 }
354
355 for (AvahiTimeout *timer : mTimers)
356 {
357 Timepoint timeout = timer->mTimeout;
358
359 if (timeout == Timepoint::min())
360 {
361 continue;
362 }
363
364 if (timeout <= now)
365 {
366 aMainloop.mTimeout = ToTimeval(Microseconds::zero());
367 break;
368 }
369 else
370 {
371 auto delay = std::chrono::duration_cast<Microseconds>(timeout - now);
372
373 if (delay < FromTimeval<Microseconds>(aMainloop.mTimeout))
374 {
375 aMainloop.mTimeout = ToTimeval(delay);
376 }
377 }
378 }
379 }
380
Process(const MainloopContext & aMainloop)381 void AvahiPoller::Process(const MainloopContext &aMainloop)
382 {
383 Timepoint now = Clock::now();
384 bool shouldReport = false;
385
386 for (AvahiWatch *watch : mWatches)
387 {
388 int fd = watch->mFd;
389 AvahiWatchEvent events = watch->mEvents;
390
391 watch->mHappened = 0;
392
393 if ((AVAHI_WATCH_IN & events) && FD_ISSET(fd, &aMainloop.mReadFdSet))
394 {
395 watch->mHappened |= AVAHI_WATCH_IN;
396 }
397
398 if ((AVAHI_WATCH_OUT & events) && FD_ISSET(fd, &aMainloop.mWriteFdSet))
399 {
400 watch->mHappened |= AVAHI_WATCH_OUT;
401 }
402
403 if ((AVAHI_WATCH_ERR & events) && FD_ISSET(fd, &aMainloop.mErrorFdSet))
404 {
405 watch->mHappened |= AVAHI_WATCH_ERR;
406 }
407
408 if (watch->mHappened != 0)
409 {
410 watch->mShouldReport = true;
411 shouldReport = true;
412 }
413 }
414
415 // When we invoke the callback for an `AvahiWatch` or `AvahiTimeout`,
416 // the Avahi module can call any of `mAvahiPoll` APIs we provided to
417 // it. For example, it can update or free any of `AvahiWatch/Timeout`
418 // entries, which in turn, modifies our `mWatches` or `mTimers` list.
419 // So, before invoking the callback, we update the entry's state and
420 // then restart the iteration over the `mWacthes` list to find the
421 // next entry to report, as the list may have changed.
422
423 while (shouldReport)
424 {
425 shouldReport = false;
426
427 for (AvahiWatch *watch : mWatches)
428 {
429 if (watch->mShouldReport)
430 {
431 shouldReport = true;
432 watch->mShouldReport = false;
433 watch->mCallback(watch, watch->mFd, WatchGetEvents(watch), watch->mContext);
434
435 break;
436 }
437 }
438 }
439
440 for (AvahiTimeout *timer : mTimers)
441 {
442 if (timer->mTimeout == Timepoint::min())
443 {
444 continue;
445 }
446
447 if (timer->mTimeout <= now)
448 {
449 timer->mShouldReport = true;
450 shouldReport = true;
451 }
452 }
453
454 while (shouldReport)
455 {
456 shouldReport = false;
457
458 for (AvahiTimeout *timer : mTimers)
459 {
460 if (timer->mShouldReport)
461 {
462 shouldReport = true;
463 timer->mShouldReport = false;
464 timer->mCallback(timer, timer->mContext);
465
466 break;
467 }
468 }
469 }
470 }
471
PublisherAvahi(StateCallback aStateCallback)472 PublisherAvahi::PublisherAvahi(StateCallback aStateCallback)
473 : mClient(nullptr)
474 , mPoller(MakeUnique<AvahiPoller>())
475 , mState(State::kIdle)
476 , mStateCallback(std::move(aStateCallback))
477 {
478 }
479
~PublisherAvahi(void)480 PublisherAvahi::~PublisherAvahi(void)
481 {
482 Stop();
483 }
484
~AvahiServiceRegistration(void)485 PublisherAvahi::AvahiServiceRegistration::~AvahiServiceRegistration(void)
486 {
487 ReleaseGroup(mEntryGroup);
488 }
489
~AvahiHostRegistration(void)490 PublisherAvahi::AvahiHostRegistration::~AvahiHostRegistration(void)
491 {
492 ReleaseGroup(mEntryGroup);
493 }
494
~AvahiKeyRegistration(void)495 PublisherAvahi::AvahiKeyRegistration::~AvahiKeyRegistration(void)
496 {
497 ReleaseGroup(mEntryGroup);
498 }
499
Start(void)500 otbrError PublisherAvahi::Start(void)
501 {
502 otbrError error = OTBR_ERROR_NONE;
503 int avahiError = AVAHI_OK;
504
505 assert(mClient == nullptr);
506
507 mClient = avahi_client_new(mPoller->GetAvahiPoll(), AVAHI_CLIENT_NO_FAIL, HandleClientState, this, &avahiError);
508
509 if (avahiError != AVAHI_OK)
510 {
511 otbrLogErr("Failed to create avahi client: %s!", avahi_strerror(avahiError));
512 error = OTBR_ERROR_MDNS;
513 }
514
515 return error;
516 }
517
IsStarted(void) const518 bool PublisherAvahi::IsStarted(void) const
519 {
520 return mClient != nullptr;
521 }
522
Stop(void)523 void PublisherAvahi::Stop(void)
524 {
525 mServiceRegistrations.clear();
526 mHostRegistrations.clear();
527
528 mSubscribedServices.clear();
529 mSubscribedHosts.clear();
530
531 if (mClient)
532 {
533 avahi_client_free(mClient);
534 mClient = nullptr;
535 }
536
537 mState = Mdns::Publisher::State::kIdle;
538 }
539
HandleClientState(AvahiClient * aClient,AvahiClientState aState,void * aContext)540 void PublisherAvahi::HandleClientState(AvahiClient *aClient, AvahiClientState aState, void *aContext)
541 {
542 static_cast<PublisherAvahi *>(aContext)->HandleClientState(aClient, aState);
543 }
544
HandleGroupState(AvahiEntryGroup * aGroup,AvahiEntryGroupState aState,void * aContext)545 void PublisherAvahi::HandleGroupState(AvahiEntryGroup *aGroup, AvahiEntryGroupState aState, void *aContext)
546 {
547 static_cast<PublisherAvahi *>(aContext)->HandleGroupState(aGroup, aState);
548 }
549
HandleGroupState(AvahiEntryGroup * aGroup,AvahiEntryGroupState aState)550 void PublisherAvahi::HandleGroupState(AvahiEntryGroup *aGroup, AvahiEntryGroupState aState)
551 {
552 switch (aState)
553 {
554 case AVAHI_ENTRY_GROUP_ESTABLISHED:
555 otbrLogInfo("Avahi group (@%p) is established", aGroup);
556 CallHostOrServiceCallback(aGroup, OTBR_ERROR_NONE);
557 break;
558
559 case AVAHI_ENTRY_GROUP_COLLISION:
560 otbrLogInfo("Avahi group (@%p) name conflicted", aGroup);
561 CallHostOrServiceCallback(aGroup, OTBR_ERROR_DUPLICATED);
562 break;
563
564 case AVAHI_ENTRY_GROUP_FAILURE:
565 otbrLogErr("Avahi group (@%p) failed: %s!", aGroup,
566 avahi_strerror(avahi_client_errno(avahi_entry_group_get_client(aGroup))));
567 CallHostOrServiceCallback(aGroup, OTBR_ERROR_MDNS);
568 break;
569
570 case AVAHI_ENTRY_GROUP_UNCOMMITED:
571 case AVAHI_ENTRY_GROUP_REGISTERING:
572 break;
573 }
574 }
575
CallHostOrServiceCallback(AvahiEntryGroup * aGroup,otbrError aError)576 void PublisherAvahi::CallHostOrServiceCallback(AvahiEntryGroup *aGroup, otbrError aError)
577 {
578 ServiceRegistration *serviceReg;
579 HostRegistration *hostReg;
580 KeyRegistration *keyReg;
581
582 if ((serviceReg = FindServiceRegistration(aGroup)) != nullptr)
583 {
584 if (aError == OTBR_ERROR_NONE)
585 {
586 serviceReg->Complete(aError);
587 }
588 else
589 {
590 RemoveServiceRegistration(serviceReg->mName, serviceReg->mType, aError);
591 }
592 }
593 else if ((hostReg = FindHostRegistration(aGroup)) != nullptr)
594 {
595 if (aError == OTBR_ERROR_NONE)
596 {
597 hostReg->Complete(aError);
598 }
599 else
600 {
601 RemoveHostRegistration(hostReg->mName, aError);
602 }
603 }
604 else if ((keyReg = FindKeyRegistration(aGroup)) != nullptr)
605 {
606 if (aError == OTBR_ERROR_NONE)
607 {
608 keyReg->Complete(aError);
609 }
610 else
611 {
612 RemoveKeyRegistration(keyReg->mName, aError);
613 }
614 }
615 else
616 {
617 otbrLogWarning("No registered service or host matches avahi group @%p", aGroup);
618 }
619 }
620
CreateGroup(AvahiClient * aClient)621 AvahiEntryGroup *PublisherAvahi::CreateGroup(AvahiClient *aClient)
622 {
623 AvahiEntryGroup *group = avahi_entry_group_new(aClient, HandleGroupState, this);
624
625 if (group == nullptr)
626 {
627 otbrLogErr("Failed to create entry avahi group: %s", avahi_strerror(avahi_client_errno(aClient)));
628 }
629
630 return group;
631 }
632
ReleaseGroup(AvahiEntryGroup * aGroup)633 void PublisherAvahi::ReleaseGroup(AvahiEntryGroup *aGroup)
634 {
635 int error;
636
637 otbrLogInfo("Releasing avahi entry group @%p", aGroup);
638
639 error = avahi_entry_group_reset(aGroup);
640
641 if (error != 0)
642 {
643 otbrLogErr("Failed to reset entry group for avahi error: %s", avahi_strerror(error));
644 }
645
646 error = avahi_entry_group_free(aGroup);
647 if (error != 0)
648 {
649 otbrLogErr("Failed to free entry group for avahi error: %s", avahi_strerror(error));
650 }
651 }
652
HandleClientState(AvahiClient * aClient,AvahiClientState aState)653 void PublisherAvahi::HandleClientState(AvahiClient *aClient, AvahiClientState aState)
654 {
655 otbrLogInfo("Avahi client state changed to %d", aState);
656
657 switch (aState)
658 {
659 case AVAHI_CLIENT_S_RUNNING:
660 // The server has startup successfully and registered its host
661 // name on the network, so it's time to create our services.
662 otbrLogInfo("Avahi client is ready");
663 mClient = aClient;
664 mState = State::kReady;
665 mStateCallback(mState);
666 break;
667
668 case AVAHI_CLIENT_FAILURE:
669 otbrLogErr("Avahi client failed to start: %s", avahi_strerror(avahi_client_errno(aClient)));
670 mState = State::kIdle;
671 mStateCallback(mState);
672 Stop();
673 Start();
674 break;
675
676 case AVAHI_CLIENT_S_COLLISION:
677 // Let's drop our registered services. When the server is back
678 // in AVAHI_SERVER_RUNNING state we will register them again
679 // with the new host name.
680 otbrLogErr("Avahi client collision detected: %s", avahi_strerror(avahi_client_errno(aClient)));
681
682 // fall through
683
684 case AVAHI_CLIENT_S_REGISTERING:
685 // The server records are now being established. This might be
686 // caused by a host name change. We need to wait for our own
687 // records to register until the host name is properly established.
688 mServiceRegistrations.clear();
689 mHostRegistrations.clear();
690 break;
691
692 case AVAHI_CLIENT_CONNECTING:
693 otbrLogInfo("Avahi client is connecting to the server");
694 break;
695 }
696 }
697
PublishServiceImpl(const std::string & aHostName,const std::string & aName,const std::string & aType,const SubTypeList & aSubTypeList,uint16_t aPort,const TxtData & aTxtData,ResultCallback && aCallback)698 otbrError PublisherAvahi::PublishServiceImpl(const std::string &aHostName,
699 const std::string &aName,
700 const std::string &aType,
701 const SubTypeList &aSubTypeList,
702 uint16_t aPort,
703 const TxtData &aTxtData,
704 ResultCallback &&aCallback)
705 {
706 otbrError error = OTBR_ERROR_NONE;
707 int avahiError = AVAHI_OK;
708 SubTypeList sortedSubTypeList = SortSubTypeList(aSubTypeList);
709 const std::string logHostName = !aHostName.empty() ? aHostName : "localhost";
710 std::string fullHostName;
711 std::string serviceName = aName;
712 AvahiEntryGroup *group = nullptr;
713
714 // Aligned with AvahiStringList
715 AvahiStringList txtBuffer[(kMaxSizeOfTxtRecord - 1) / sizeof(AvahiStringList) + 1];
716 AvahiStringList *txtHead = nullptr;
717
718 VerifyOrExit(mState == State::kReady, error = OTBR_ERROR_INVALID_STATE);
719 VerifyOrExit(mClient != nullptr, error = OTBR_ERROR_INVALID_STATE);
720
721 if (!aHostName.empty())
722 {
723 fullHostName = MakeFullHostName(aHostName);
724 }
725 if (serviceName.empty())
726 {
727 serviceName = avahi_client_get_host_name(mClient);
728 }
729
730 aCallback = HandleDuplicateServiceRegistration(aHostName, serviceName, aType, sortedSubTypeList, aPort, aTxtData,
731 std::move(aCallback));
732 VerifyOrExit(!aCallback.IsNull());
733
734 SuccessOrExit(error = TxtDataToAvahiStringList(aTxtData, txtBuffer, sizeof(txtBuffer), txtHead));
735 VerifyOrExit((group = CreateGroup(mClient)) != nullptr, error = OTBR_ERROR_MDNS);
736 avahiError = avahi_entry_group_add_service_strlst(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AvahiPublishFlags{},
737 serviceName.c_str(), aType.c_str(),
738 /* domain */ nullptr, fullHostName.c_str(), aPort, txtHead);
739 VerifyOrExit(avahiError == AVAHI_OK);
740
741 for (const std::string &subType : aSubTypeList)
742 {
743 otbrLogInfo("Add subtype %s for service %s.%s", subType.c_str(), serviceName.c_str(), aType.c_str());
744 std::string fullSubType = subType + "._sub." + aType;
745 avahiError = avahi_entry_group_add_service_subtype(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
746 AvahiPublishFlags{}, serviceName.c_str(), aType.c_str(),
747 /* domain */ nullptr, fullSubType.c_str());
748 VerifyOrExit(avahiError == AVAHI_OK);
749 }
750
751 otbrLogInfo("Commit avahi service %s.%s", serviceName.c_str(), aType.c_str());
752 avahiError = avahi_entry_group_commit(group);
753 VerifyOrExit(avahiError == AVAHI_OK);
754
755 AddServiceRegistration(std::unique_ptr<AvahiServiceRegistration>(new AvahiServiceRegistration(
756 aHostName, serviceName, aType, sortedSubTypeList, aPort, aTxtData, std::move(aCallback), group, this)));
757
758 exit:
759 if (avahiError != AVAHI_OK || error != OTBR_ERROR_NONE)
760 {
761 if (avahiError != AVAHI_OK)
762 {
763 error = OTBR_ERROR_MDNS;
764 otbrLogErr("Failed to publish service for avahi error: %s!", avahi_strerror(avahiError));
765 }
766
767 if (group != nullptr)
768 {
769 ReleaseGroup(group);
770 }
771 std::move(aCallback)(error);
772 }
773 return error;
774 }
775
UnpublishService(const std::string & aName,const std::string & aType,ResultCallback && aCallback)776 void PublisherAvahi::UnpublishService(const std::string &aName, const std::string &aType, ResultCallback &&aCallback)
777 {
778 otbrError error = OTBR_ERROR_NONE;
779
780 VerifyOrExit(mState == Publisher::State::kReady, error = OTBR_ERROR_INVALID_STATE);
781 RemoveServiceRegistration(aName, aType, OTBR_ERROR_ABORTED);
782
783 exit:
784 std::move(aCallback)(error);
785 }
786
PublishHostImpl(const std::string & aName,const AddressList & aAddresses,ResultCallback && aCallback)787 otbrError PublisherAvahi::PublishHostImpl(const std::string &aName,
788 const AddressList &aAddresses,
789 ResultCallback &&aCallback)
790 {
791 otbrError error = OTBR_ERROR_NONE;
792 int avahiError = AVAHI_OK;
793 std::string fullHostName;
794 AvahiEntryGroup *group = nullptr;
795
796 VerifyOrExit(mState == State::kReady, error = OTBR_ERROR_INVALID_STATE);
797 VerifyOrExit(mClient != nullptr, error = OTBR_ERROR_INVALID_STATE);
798
799 aCallback = HandleDuplicateHostRegistration(aName, aAddresses, std::move(aCallback));
800 VerifyOrExit(!aCallback.IsNull());
801 VerifyOrExit(!aAddresses.empty(), std::move(aCallback)(OTBR_ERROR_NONE));
802
803 VerifyOrExit((group = CreateGroup(mClient)) != nullptr, error = OTBR_ERROR_MDNS);
804
805 fullHostName = MakeFullHostName(aName);
806 for (const auto &address : aAddresses)
807 {
808 AvahiAddress avahiAddress;
809
810 avahiAddress.proto = AVAHI_PROTO_INET6;
811 memcpy(avahiAddress.data.ipv6.address, address.m8, sizeof(address.m8));
812 avahiError = avahi_entry_group_add_address(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AVAHI_PUBLISH_NO_REVERSE,
813 fullHostName.c_str(), &avahiAddress);
814 VerifyOrExit(avahiError == AVAHI_OK);
815 }
816
817 otbrLogInfo("Commit avahi host %s", aName.c_str());
818 avahiError = avahi_entry_group_commit(group);
819 VerifyOrExit(avahiError == AVAHI_OK);
820
821 AddHostRegistration(std::unique_ptr<AvahiHostRegistration>(
822 new AvahiHostRegistration(aName, aAddresses, std::move(aCallback), group, this)));
823
824 exit:
825 if (avahiError != AVAHI_OK || error != OTBR_ERROR_NONE)
826 {
827 if (avahiError != AVAHI_OK)
828 {
829 error = OTBR_ERROR_MDNS;
830 otbrLogErr("Failed to publish host for avahi error: %s!", avahi_strerror(avahiError));
831 }
832
833 if (group != nullptr)
834 {
835 ReleaseGroup(group);
836 }
837 std::move(aCallback)(error);
838 }
839 return error;
840 }
841
UnpublishHost(const std::string & aName,ResultCallback && aCallback)842 void PublisherAvahi::UnpublishHost(const std::string &aName, ResultCallback &&aCallback)
843 {
844 otbrError error = OTBR_ERROR_NONE;
845
846 VerifyOrExit(mState == Publisher::State::kReady, error = OTBR_ERROR_INVALID_STATE);
847 RemoveHostRegistration(aName, OTBR_ERROR_ABORTED);
848
849 exit:
850 std::move(aCallback)(error);
851 }
852
PublishKeyImpl(const std::string & aName,const KeyData & aKeyData,ResultCallback && aCallback)853 otbrError PublisherAvahi::PublishKeyImpl(const std::string &aName, const KeyData &aKeyData, ResultCallback &&aCallback)
854 {
855 otbrError error = OTBR_ERROR_NONE;
856 int avahiError = AVAHI_OK;
857 std::string fullKeyName;
858 AvahiEntryGroup *group = nullptr;
859
860 VerifyOrExit(mState == State::kReady, error = OTBR_ERROR_INVALID_STATE);
861 VerifyOrExit(mClient != nullptr, error = OTBR_ERROR_INVALID_STATE);
862
863 aCallback = HandleDuplicateKeyRegistration(aName, aKeyData, std::move(aCallback));
864 VerifyOrExit(!aCallback.IsNull());
865
866 VerifyOrExit((group = CreateGroup(mClient)) != nullptr, error = OTBR_ERROR_MDNS);
867
868 fullKeyName = MakeFullKeyName(aName);
869
870 avahiError = avahi_entry_group_add_record(group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, AVAHI_PUBLISH_UNIQUE,
871 fullKeyName.c_str(), AVAHI_DNS_CLASS_IN, kDnsKeyRecordType, kDefaultTtl,
872 aKeyData.data(), aKeyData.size());
873 VerifyOrExit(avahiError == AVAHI_OK);
874
875 otbrLogInfo("Commit avahi key record for %s", aName.c_str());
876 avahiError = avahi_entry_group_commit(group);
877 VerifyOrExit(avahiError == AVAHI_OK);
878
879 AddKeyRegistration(std::unique_ptr<AvahiKeyRegistration>(
880 new AvahiKeyRegistration(aName, aKeyData, std::move(aCallback), group, this)));
881
882 exit:
883 if (avahiError != AVAHI_OK || error != OTBR_ERROR_NONE)
884 {
885 if (avahiError != AVAHI_OK)
886 {
887 error = OTBR_ERROR_MDNS;
888 otbrLogErr("Failed to publish key record - avahi error: %s!", avahi_strerror(avahiError));
889 }
890
891 if (group != nullptr)
892 {
893 ReleaseGroup(group);
894 }
895 std::move(aCallback)(error);
896 }
897 return error;
898 }
899
UnpublishKey(const std::string & aName,ResultCallback && aCallback)900 void PublisherAvahi::UnpublishKey(const std::string &aName, ResultCallback &&aCallback)
901 {
902 otbrError error = OTBR_ERROR_NONE;
903
904 VerifyOrExit(mState == Publisher::State::kReady, error = OTBR_ERROR_INVALID_STATE);
905 RemoveKeyRegistration(aName, OTBR_ERROR_ABORTED);
906
907 exit:
908 std::move(aCallback)(error);
909 }
910
TxtDataToAvahiStringList(const TxtData & aTxtData,AvahiStringList * aBuffer,size_t aBufferSize,AvahiStringList * & aHead)911 otbrError PublisherAvahi::TxtDataToAvahiStringList(const TxtData &aTxtData,
912 AvahiStringList *aBuffer,
913 size_t aBufferSize,
914 AvahiStringList *&aHead)
915 {
916 otbrError error = OTBR_ERROR_NONE;
917 size_t used = 0;
918 AvahiStringList *last = nullptr;
919 AvahiStringList *curr = aBuffer;
920 const uint8_t *next;
921 const uint8_t *data = aTxtData.data();
922 const uint8_t *dataEnd = aTxtData.data() + aTxtData.size();
923
924 aHead = nullptr;
925
926 while (data < dataEnd)
927 {
928 uint8_t entryLength = *data++;
929 size_t needed = sizeof(AvahiStringList) - sizeof(AvahiStringList::text) + entryLength;
930
931 if (entryLength == 0)
932 {
933 continue;
934 }
935
936 VerifyOrExit(data + entryLength <= dataEnd, error = OTBR_ERROR_PARSE);
937
938 VerifyOrExit(used + needed <= aBufferSize, error = OTBR_ERROR_INVALID_ARGS);
939 curr->next = last;
940 last = curr;
941
942 memcpy(curr->text, data, entryLength);
943 curr->size = entryLength;
944
945 data += entryLength;
946
947 next = curr->text + curr->size;
948 curr = OTBR_ALIGNED(next, AvahiStringList *);
949 used = static_cast<size_t>(reinterpret_cast<uint8_t *>(curr) - reinterpret_cast<uint8_t *>(aBuffer));
950 }
951
952 aHead = last;
953
954 exit:
955 return error;
956 }
957
FindServiceRegistration(const AvahiEntryGroup * aEntryGroup)958 Publisher::ServiceRegistration *PublisherAvahi::FindServiceRegistration(const AvahiEntryGroup *aEntryGroup)
959 {
960 ServiceRegistration *result = nullptr;
961
962 for (const auto &kv : mServiceRegistrations)
963 {
964 const auto &serviceReg = static_cast<const AvahiServiceRegistration &>(*kv.second);
965 if (serviceReg.GetEntryGroup() == aEntryGroup)
966 {
967 result = kv.second.get();
968 break;
969 }
970 }
971
972 return result;
973 }
974
FindHostRegistration(const AvahiEntryGroup * aEntryGroup)975 Publisher::HostRegistration *PublisherAvahi::FindHostRegistration(const AvahiEntryGroup *aEntryGroup)
976 {
977 HostRegistration *result = nullptr;
978
979 for (const auto &kv : mHostRegistrations)
980 {
981 const auto &hostReg = static_cast<const AvahiHostRegistration &>(*kv.second);
982 if (hostReg.GetEntryGroup() == aEntryGroup)
983 {
984 result = kv.second.get();
985 break;
986 }
987 }
988
989 return result;
990 }
991
FindKeyRegistration(const AvahiEntryGroup * aEntryGroup)992 Publisher::KeyRegistration *PublisherAvahi::FindKeyRegistration(const AvahiEntryGroup *aEntryGroup)
993 {
994 KeyRegistration *result = nullptr;
995
996 for (const auto &entry : mKeyRegistrations)
997 {
998 const auto &keyReg = static_cast<const AvahiKeyRegistration &>(*entry.second);
999 if (keyReg.GetEntryGroup() == aEntryGroup)
1000 {
1001 result = entry.second.get();
1002 break;
1003 }
1004 }
1005
1006 return result;
1007 }
1008
SubscribeService(const std::string & aType,const std::string & aInstanceName)1009 void PublisherAvahi::SubscribeService(const std::string &aType, const std::string &aInstanceName)
1010 {
1011 auto service = MakeUnique<ServiceSubscription>(*this, aType, aInstanceName);
1012
1013 VerifyOrExit(mState == Publisher::State::kReady);
1014 mSubscribedServices.push_back(std::move(service));
1015
1016 otbrLogInfo("Subscribe service %s.%s (total %zu)", aInstanceName.c_str(), aType.c_str(),
1017 mSubscribedServices.size());
1018
1019 if (aInstanceName.empty())
1020 {
1021 mSubscribedServices.back()->Browse();
1022 }
1023 else
1024 {
1025 mSubscribedServices.back()->Resolve(AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, aInstanceName, aType);
1026 }
1027
1028 exit:
1029 return;
1030 }
1031
UnsubscribeService(const std::string & aType,const std::string & aInstanceName)1032 void PublisherAvahi::UnsubscribeService(const std::string &aType, const std::string &aInstanceName)
1033 {
1034 ServiceSubscriptionList::iterator it;
1035
1036 VerifyOrExit(mState == Publisher::State::kReady);
1037 it = std::find_if(mSubscribedServices.begin(), mSubscribedServices.end(),
1038 [&aType, &aInstanceName](const std::unique_ptr<ServiceSubscription> &aService) {
1039 return aService->mType == aType && aService->mInstanceName == aInstanceName;
1040 });
1041
1042 VerifyOrExit(it != mSubscribedServices.end());
1043
1044 {
1045 std::unique_ptr<ServiceSubscription> service = std::move(*it);
1046
1047 mSubscribedServices.erase(it);
1048 service->Release();
1049 }
1050
1051 otbrLogInfo("Unsubscribe service %s.%s (left %zu)", aInstanceName.c_str(), aType.c_str(),
1052 mSubscribedServices.size());
1053
1054 exit:
1055 return;
1056 }
1057
OnServiceResolveFailedImpl(const std::string & aType,const std::string & aInstanceName,int32_t aErrorCode)1058 void PublisherAvahi::OnServiceResolveFailedImpl(const std::string &aType,
1059 const std::string &aInstanceName,
1060 int32_t aErrorCode)
1061 {
1062 otbrLogWarning("Resolve service %s.%s failed: %s", aInstanceName.c_str(), aType.c_str(),
1063 avahi_strerror(aErrorCode));
1064 }
1065
OnHostResolveFailedImpl(const std::string & aHostName,int32_t aErrorCode)1066 void PublisherAvahi::OnHostResolveFailedImpl(const std::string &aHostName, int32_t aErrorCode)
1067 {
1068 otbrLogWarning("Resolve host %s failed: %s", aHostName.c_str(), avahi_strerror(aErrorCode));
1069 }
1070
DnsErrorToOtbrError(int32_t aErrorCode)1071 otbrError PublisherAvahi::DnsErrorToOtbrError(int32_t aErrorCode)
1072 {
1073 return otbr::Mdns::DnsErrorToOtbrError(aErrorCode);
1074 }
1075
SubscribeHost(const std::string & aHostName)1076 void PublisherAvahi::SubscribeHost(const std::string &aHostName)
1077 {
1078 auto host = MakeUnique<HostSubscription>(*this, aHostName);
1079
1080 VerifyOrExit(mState == Publisher::State::kReady);
1081
1082 mSubscribedHosts.push_back(std::move(host));
1083
1084 otbrLogInfo("Subscribe host %s (total %zu)", aHostName.c_str(), mSubscribedHosts.size());
1085
1086 mSubscribedHosts.back()->Resolve();
1087
1088 exit:
1089 return;
1090 }
1091
UnsubscribeHost(const std::string & aHostName)1092 void PublisherAvahi::UnsubscribeHost(const std::string &aHostName)
1093 {
1094 HostSubscriptionList::iterator it;
1095
1096 VerifyOrExit(mState == Publisher::State::kReady);
1097 it = std::find_if(
1098 mSubscribedHosts.begin(), mSubscribedHosts.end(),
1099 [&aHostName](const std::unique_ptr<HostSubscription> &aHost) { return aHost->mHostName == aHostName; });
1100
1101 VerifyOrExit(it != mSubscribedHosts.end());
1102
1103 {
1104 std::unique_ptr<HostSubscription> host = std::move(*it);
1105
1106 mSubscribedHosts.erase(it);
1107 host->Release();
1108 }
1109
1110 otbrLogInfo("Unsubscribe host %s (remaining %zu)", aHostName.c_str(), mSubscribedHosts.size());
1111
1112 exit:
1113 return;
1114 }
1115
Create(StateCallback aStateCallback)1116 Publisher *Publisher::Create(StateCallback aStateCallback)
1117 {
1118 return new PublisherAvahi(std::move(aStateCallback));
1119 }
1120
Destroy(Publisher * aPublisher)1121 void Publisher::Destroy(Publisher *aPublisher)
1122 {
1123 delete static_cast<PublisherAvahi *>(aPublisher);
1124 }
1125
Browse(void)1126 void PublisherAvahi::ServiceSubscription::Browse(void)
1127 {
1128 assert(mPublisherAvahi->mClient != nullptr);
1129
1130 otbrLogInfo("Browse service %s", mType.c_str());
1131 mServiceBrowser =
1132 avahi_service_browser_new(mPublisherAvahi->mClient, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, mType.c_str(),
1133 /* domain */ nullptr, static_cast<AvahiLookupFlags>(0), HandleBrowseResult, this);
1134 if (!mServiceBrowser)
1135 {
1136 otbrLogWarning("Failed to browse service %s: %s", mType.c_str(),
1137 avahi_strerror(avahi_client_errno(mPublisherAvahi->mClient)));
1138 }
1139 }
1140
Release(void)1141 void PublisherAvahi::ServiceSubscription::Release(void)
1142 {
1143 std::vector<std::string> instanceNames;
1144
1145 for (const auto &resolvers : mServiceResolvers)
1146 {
1147 instanceNames.push_back(resolvers.first);
1148 }
1149 for (const auto &name : instanceNames)
1150 {
1151 RemoveServiceResolver(name);
1152 }
1153
1154 if (mServiceBrowser != nullptr)
1155 {
1156 avahi_service_browser_free(mServiceBrowser);
1157 mServiceBrowser = nullptr;
1158 }
1159 }
1160
HandleBrowseResult(AvahiServiceBrowser * aServiceBrowser,AvahiIfIndex aInterfaceIndex,AvahiProtocol aProtocol,AvahiBrowserEvent aEvent,const char * aName,const char * aType,const char * aDomain,AvahiLookupResultFlags aFlags,void * aContext)1161 void PublisherAvahi::ServiceSubscription::HandleBrowseResult(AvahiServiceBrowser *aServiceBrowser,
1162 AvahiIfIndex aInterfaceIndex,
1163 AvahiProtocol aProtocol,
1164 AvahiBrowserEvent aEvent,
1165 const char *aName,
1166 const char *aType,
1167 const char *aDomain,
1168 AvahiLookupResultFlags aFlags,
1169 void *aContext)
1170 {
1171 static_cast<PublisherAvahi::ServiceSubscription *>(aContext)->HandleBrowseResult(
1172 aServiceBrowser, aInterfaceIndex, aProtocol, aEvent, aName, aType, aDomain, aFlags);
1173 }
1174
HandleBrowseResult(AvahiServiceBrowser * aServiceBrowser,AvahiIfIndex aInterfaceIndex,AvahiProtocol aProtocol,AvahiBrowserEvent aEvent,const char * aName,const char * aType,const char * aDomain,AvahiLookupResultFlags aFlags)1175 void PublisherAvahi::ServiceSubscription::HandleBrowseResult(AvahiServiceBrowser *aServiceBrowser,
1176 AvahiIfIndex aInterfaceIndex,
1177 AvahiProtocol aProtocol,
1178 AvahiBrowserEvent aEvent,
1179 const char *aName,
1180 const char *aType,
1181 const char *aDomain,
1182 AvahiLookupResultFlags aFlags)
1183 {
1184 OTBR_UNUSED_VARIABLE(aServiceBrowser);
1185 OTBR_UNUSED_VARIABLE(aProtocol);
1186 OTBR_UNUSED_VARIABLE(aDomain);
1187
1188 assert(mServiceBrowser == aServiceBrowser);
1189
1190 otbrLogInfo("Browse service reply: %s.%s proto %d inf %u event %d flags %d", aName, aType, aProtocol,
1191 aInterfaceIndex, static_cast<int>(aEvent), static_cast<int>(aFlags));
1192
1193 switch (aEvent)
1194 {
1195 case AVAHI_BROWSER_NEW:
1196 Resolve(aInterfaceIndex, aProtocol, aName, aType);
1197 break;
1198 case AVAHI_BROWSER_REMOVE:
1199 mPublisherAvahi->OnServiceRemoved(static_cast<uint32_t>(aInterfaceIndex), aType, aName);
1200 RemoveServiceResolver(aName);
1201 break;
1202 case AVAHI_BROWSER_CACHE_EXHAUSTED:
1203 case AVAHI_BROWSER_ALL_FOR_NOW:
1204 // do nothing
1205 break;
1206 case AVAHI_BROWSER_FAILURE:
1207 mPublisherAvahi->OnServiceResolveFailed(aType, aName, avahi_client_errno(mPublisherAvahi->mClient));
1208 break;
1209 }
1210 }
1211
Resolve(uint32_t aInterfaceIndex,AvahiProtocol aProtocol,const std::string & aInstanceName,const std::string & aType)1212 void PublisherAvahi::ServiceSubscription::Resolve(uint32_t aInterfaceIndex,
1213 AvahiProtocol aProtocol,
1214 const std::string &aInstanceName,
1215 const std::string &aType)
1216 {
1217 auto serviceResolver = MakeUnique<ServiceResolver>();
1218
1219 mPublisherAvahi->mServiceInstanceResolutionBeginTime[std::make_pair(aInstanceName, aType)] = Clock::now();
1220
1221 otbrLogInfo("Resolve service %s.%s inf %" PRIu32, aInstanceName.c_str(), aType.c_str(), aInterfaceIndex);
1222
1223 serviceResolver->mType = aType;
1224 serviceResolver->mPublisherAvahi = this->mPublisherAvahi;
1225 serviceResolver->mServiceResolver = avahi_service_resolver_new(
1226 mPublisherAvahi->mClient, aInterfaceIndex, aProtocol, aInstanceName.c_str(), aType.c_str(),
1227 /* domain */ nullptr, AVAHI_PROTO_UNSPEC, static_cast<AvahiLookupFlags>(AVAHI_LOOKUP_NO_ADDRESS),
1228 &ServiceResolver::HandleResolveServiceResult, serviceResolver.get());
1229
1230 if (serviceResolver->mServiceResolver != nullptr)
1231 {
1232 AddServiceResolver(aInstanceName, serviceResolver.release());
1233 }
1234 else
1235 {
1236 otbrLogErr("Failed to resolve serivce %s: %s", mType.c_str(),
1237 avahi_strerror(avahi_client_errno(mPublisherAvahi->mClient)));
1238 }
1239 }
1240
HandleResolveServiceResult(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)1241 void PublisherAvahi::ServiceResolver::HandleResolveServiceResult(AvahiServiceResolver *aServiceResolver,
1242 AvahiIfIndex aInterfaceIndex,
1243 AvahiProtocol aProtocol,
1244 AvahiResolverEvent aEvent,
1245 const char *aName,
1246 const char *aType,
1247 const char *aDomain,
1248 const char *aHostName,
1249 const AvahiAddress *aAddress,
1250 uint16_t aPort,
1251 AvahiStringList *aTxt,
1252 AvahiLookupResultFlags aFlags,
1253 void *aContext)
1254 {
1255 static_cast<PublisherAvahi::ServiceResolver *>(aContext)->HandleResolveServiceResult(
1256 aServiceResolver, aInterfaceIndex, aProtocol, aEvent, aName, aType, aDomain, aHostName, aAddress, aPort, aTxt,
1257 aFlags);
1258 }
1259
HandleResolveServiceResult(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)1260 void PublisherAvahi::ServiceResolver::HandleResolveServiceResult(AvahiServiceResolver *aServiceResolver,
1261 AvahiIfIndex aInterfaceIndex,
1262 AvahiProtocol aProtocol,
1263 AvahiResolverEvent aEvent,
1264 const char *aName,
1265 const char *aType,
1266 const char *aDomain,
1267 const char *aHostName,
1268 const AvahiAddress *aAddress,
1269 uint16_t aPort,
1270 AvahiStringList *aTxt,
1271 AvahiLookupResultFlags aFlags)
1272 {
1273 OT_UNUSED_VARIABLE(aServiceResolver);
1274 OT_UNUSED_VARIABLE(aInterfaceIndex);
1275 OT_UNUSED_VARIABLE(aProtocol);
1276 OT_UNUSED_VARIABLE(aType);
1277 OT_UNUSED_VARIABLE(aDomain);
1278 OT_UNUSED_VARIABLE(aAddress);
1279
1280 size_t totalTxtSize = 0;
1281 bool resolved = false;
1282 int avahiError = AVAHI_OK;
1283
1284 otbrLog(aEvent == AVAHI_RESOLVER_FOUND ? OTBR_LOG_INFO : OTBR_LOG_WARNING, OTBR_LOG_TAG,
1285 "Resolve service reply: protocol %d %s.%s.%s = host %s port %" PRIu16 " flags %d event %d", aProtocol,
1286 aName, aType, aDomain, aHostName, aPort, static_cast<int>(aFlags), static_cast<int>(aEvent));
1287
1288 VerifyOrExit(aEvent == AVAHI_RESOLVER_FOUND, avahiError = avahi_client_errno(mPublisherAvahi->mClient));
1289 VerifyOrExit(aHostName != nullptr, avahiError = AVAHI_ERR_INVALID_HOST_NAME);
1290
1291 mInstanceInfo.mNetifIndex = static_cast<uint32_t>(aInterfaceIndex);
1292 mInstanceInfo.mName = aName;
1293 mInstanceInfo.mHostName = std::string(aHostName) + ".";
1294 mInstanceInfo.mPort = aPort;
1295
1296 otbrLogInfo("Resolve service reply: flags=%u, host=%s", aFlags, aHostName);
1297
1298 // TODO priority
1299 // TODO weight
1300 // TODO use a more proper TTL
1301 mInstanceInfo.mTtl = kDefaultTtl;
1302 for (auto p = aTxt; p; p = avahi_string_list_get_next(p))
1303 {
1304 totalTxtSize += avahi_string_list_get_size(p) + 1;
1305 }
1306 mInstanceInfo.mTxtData.resize(totalTxtSize);
1307 avahi_string_list_serialize(aTxt, mInstanceInfo.mTxtData.data(), totalTxtSize);
1308
1309 // NOTE: Avahi only returns one of the host's addresses in the service resolution callback. However, the address may
1310 // be link-local so it may not be preferred from Thread's perspective. We want to go through the complete list of
1311 // addresses associated with the host and choose a routable address. Therefore, as below we will resolve the host
1312 // and go through all its addresses.
1313
1314 resolved = true;
1315
1316 exit:
1317 if (resolved)
1318 {
1319 // In case the callback is triggered when a service instance is updated, there may already be a record browser.
1320 // We should free it before switching to the new record browser.
1321 if (mRecordBrowser)
1322 {
1323 avahi_record_browser_free(mRecordBrowser);
1324 mRecordBrowser = nullptr;
1325 mInstanceInfo.mAddresses.clear();
1326 }
1327 // NOTE: This `ServiceResolver` object may be freed in `OnServiceResolved`.
1328 mRecordBrowser = avahi_record_browser_new(mPublisherAvahi->mClient, aInterfaceIndex, AVAHI_PROTO_UNSPEC,
1329 aHostName, AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_AAAA,
1330 static_cast<AvahiLookupFlags>(0), HandleResolveHostResult, this);
1331 if (!mRecordBrowser)
1332 {
1333 resolved = false;
1334 avahiError = avahi_client_errno(mPublisherAvahi->mClient);
1335 }
1336 }
1337 if (!resolved && avahiError != AVAHI_OK)
1338 {
1339 mPublisherAvahi->OnServiceResolveFailed(aType, aName, avahiError);
1340 }
1341 }
1342
HandleResolveHostResult(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)1343 void PublisherAvahi::ServiceResolver::HandleResolveHostResult(AvahiRecordBrowser *aRecordBrowser,
1344 AvahiIfIndex aInterfaceIndex,
1345 AvahiProtocol aProtocol,
1346 AvahiBrowserEvent aEvent,
1347 const char *aName,
1348 uint16_t aClazz,
1349 uint16_t aType,
1350 const void *aRdata,
1351 size_t aSize,
1352 AvahiLookupResultFlags aFlags,
1353 void *aContext)
1354 {
1355 static_cast<PublisherAvahi::ServiceResolver *>(aContext)->HandleResolveHostResult(
1356 aRecordBrowser, aInterfaceIndex, aProtocol, aEvent, aName, aClazz, aType, aRdata, aSize, aFlags);
1357 }
1358
HandleResolveHostResult(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)1359 void PublisherAvahi::ServiceResolver::HandleResolveHostResult(AvahiRecordBrowser *aRecordBrowser,
1360 AvahiIfIndex aInterfaceIndex,
1361 AvahiProtocol aProtocol,
1362 AvahiBrowserEvent aEvent,
1363 const char *aName,
1364 uint16_t aClazz,
1365 uint16_t aType,
1366 const void *aRdata,
1367 size_t aSize,
1368 AvahiLookupResultFlags aFlags)
1369 {
1370 OTBR_UNUSED_VARIABLE(aRecordBrowser);
1371 OTBR_UNUSED_VARIABLE(aInterfaceIndex);
1372 OTBR_UNUSED_VARIABLE(aProtocol);
1373 OTBR_UNUSED_VARIABLE(aEvent);
1374 OTBR_UNUSED_VARIABLE(aClazz);
1375 OTBR_UNUSED_VARIABLE(aType);
1376 OTBR_UNUSED_VARIABLE(aFlags);
1377
1378 Ip6Address address;
1379 bool resolved = false;
1380 int avahiError = AVAHI_OK;
1381
1382 otbrLog(aEvent != AVAHI_BROWSER_FAILURE ? OTBR_LOG_INFO : OTBR_LOG_WARNING, OTBR_LOG_TAG,
1383 "Resolve host reply: %s inf %d protocol %d class %" PRIu16 " type %" PRIu16 " size %zu flags %d event %d",
1384 aName, aInterfaceIndex, aProtocol, aClazz, aType, aSize, static_cast<int>(aFlags),
1385 static_cast<int>(aEvent));
1386
1387 VerifyOrExit(aEvent == AVAHI_BROWSER_NEW || aEvent == AVAHI_BROWSER_REMOVE);
1388 VerifyOrExit(aSize == OTBR_IP6_ADDRESS_SIZE || aSize == OTBR_IP4_ADDRESS_SIZE,
1389 otbrLogErr("Unexpected address data length: %zu", aSize), avahiError = AVAHI_ERR_INVALID_ADDRESS);
1390 VerifyOrExit(aSize == OTBR_IP6_ADDRESS_SIZE, otbrLogInfo("IPv4 address ignored"),
1391 avahiError = AVAHI_ERR_INVALID_ADDRESS);
1392 address = Ip6Address(*static_cast<const uint8_t(*)[OTBR_IP6_ADDRESS_SIZE]>(aRdata));
1393
1394 VerifyOrExit(!address.IsLinkLocal() && !address.IsMulticast() && !address.IsLoopback() && !address.IsUnspecified(),
1395 avahiError = AVAHI_ERR_INVALID_ADDRESS);
1396 otbrLogInfo("Resolved host address: %s %s", aEvent == AVAHI_BROWSER_NEW ? "add" : "remove",
1397 address.ToString().c_str());
1398 if (aEvent == AVAHI_BROWSER_NEW)
1399 {
1400 mInstanceInfo.AddAddress(address);
1401 }
1402 else
1403 {
1404 mInstanceInfo.RemoveAddress(address);
1405 }
1406 resolved = true;
1407
1408 exit:
1409 if (resolved)
1410 {
1411 // NOTE: This `HostSubscrption` object may be freed in `OnHostResolved`.
1412 mPublisherAvahi->OnServiceResolved(mType, mInstanceInfo);
1413 }
1414 else if (avahiError != AVAHI_OK)
1415 {
1416 mPublisherAvahi->OnServiceResolveFailed(mType, mInstanceInfo.mName, avahiError);
1417 }
1418 }
1419
AddServiceResolver(const std::string & aInstanceName,ServiceResolver * aServiceResolver)1420 void PublisherAvahi::ServiceSubscription::AddServiceResolver(const std::string &aInstanceName,
1421 ServiceResolver *aServiceResolver)
1422 {
1423 assert(aServiceResolver != nullptr);
1424 mServiceResolvers[aInstanceName].insert(aServiceResolver);
1425
1426 otbrLogDebug("Added service resolver for instance %s", aInstanceName.c_str());
1427 }
1428
RemoveServiceResolver(const std::string & aInstanceName)1429 void PublisherAvahi::ServiceSubscription::RemoveServiceResolver(const std::string &aInstanceName)
1430 {
1431 int numResolvers = 0;
1432
1433 VerifyOrExit(mServiceResolvers.find(aInstanceName) != mServiceResolvers.end());
1434
1435 numResolvers = mServiceResolvers[aInstanceName].size();
1436
1437 for (auto resolver : mServiceResolvers[aInstanceName])
1438 {
1439 delete resolver;
1440 }
1441
1442 mServiceResolvers.erase(aInstanceName);
1443
1444 exit:
1445 otbrLogDebug("Removed %d service resolver for instance %s", numResolvers, aInstanceName.c_str());
1446 return;
1447 }
1448
Release(void)1449 void PublisherAvahi::HostSubscription::Release(void)
1450 {
1451 if (mRecordBrowser != nullptr)
1452 {
1453 avahi_record_browser_free(mRecordBrowser);
1454 mRecordBrowser = nullptr;
1455 }
1456 }
1457
Resolve(void)1458 void PublisherAvahi::HostSubscription::Resolve(void)
1459 {
1460 std::string fullHostName = MakeFullHostName(mHostName);
1461
1462 mPublisherAvahi->mHostResolutionBeginTime[mHostName] = Clock::now();
1463
1464 otbrLogInfo("Resolve host %s inf %d", fullHostName.c_str(), static_cast<int>(AVAHI_IF_UNSPEC));
1465 mRecordBrowser = avahi_record_browser_new(mPublisherAvahi->mClient, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
1466 fullHostName.c_str(), AVAHI_DNS_CLASS_IN, AVAHI_DNS_TYPE_AAAA,
1467 static_cast<AvahiLookupFlags>(0), HandleResolveResult, this);
1468 if (!mRecordBrowser)
1469 {
1470 otbrLogErr("Failed to resolve host %s: %s", fullHostName.c_str(),
1471 avahi_strerror(avahi_client_errno(mPublisherAvahi->mClient)));
1472 }
1473 }
1474
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)1475 void PublisherAvahi::HostSubscription::HandleResolveResult(AvahiRecordBrowser *aRecordBrowser,
1476 AvahiIfIndex aInterfaceIndex,
1477 AvahiProtocol aProtocol,
1478 AvahiBrowserEvent aEvent,
1479 const char *aName,
1480 uint16_t aClazz,
1481 uint16_t aType,
1482 const void *aRdata,
1483 size_t aSize,
1484 AvahiLookupResultFlags aFlags,
1485 void *aContext)
1486 {
1487 static_cast<PublisherAvahi::HostSubscription *>(aContext)->HandleResolveResult(
1488 aRecordBrowser, aInterfaceIndex, aProtocol, aEvent, aName, aClazz, aType, aRdata, aSize, aFlags);
1489 }
1490
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)1491 void PublisherAvahi::HostSubscription::HandleResolveResult(AvahiRecordBrowser *aRecordBrowser,
1492 AvahiIfIndex aInterfaceIndex,
1493 AvahiProtocol aProtocol,
1494 AvahiBrowserEvent aEvent,
1495 const char *aName,
1496 uint16_t aClazz,
1497 uint16_t aType,
1498 const void *aRdata,
1499 size_t aSize,
1500 AvahiLookupResultFlags aFlags)
1501 {
1502 OTBR_UNUSED_VARIABLE(aRecordBrowser);
1503 OTBR_UNUSED_VARIABLE(aProtocol);
1504 OTBR_UNUSED_VARIABLE(aEvent);
1505 OTBR_UNUSED_VARIABLE(aClazz);
1506 OTBR_UNUSED_VARIABLE(aType);
1507 OTBR_UNUSED_VARIABLE(aFlags);
1508
1509 Ip6Address address;
1510 bool resolved = false;
1511 int avahiError = AVAHI_OK;
1512
1513 otbrLog(aEvent != AVAHI_BROWSER_FAILURE ? OTBR_LOG_INFO : OTBR_LOG_WARNING, OTBR_LOG_TAG,
1514 "Resolve host reply: %s inf %d protocol %d class %" PRIu16 " type %" PRIu16 " size %zu flags %d event %d",
1515 aName, aInterfaceIndex, aProtocol, aClazz, aType, aSize, static_cast<int>(aFlags),
1516 static_cast<int>(aEvent));
1517
1518 VerifyOrExit(aEvent == AVAHI_BROWSER_NEW || aEvent == AVAHI_BROWSER_REMOVE);
1519 VerifyOrExit(aSize == OTBR_IP6_ADDRESS_SIZE || aSize == OTBR_IP4_ADDRESS_SIZE,
1520 otbrLogErr("Unexpected address data length: %zu", aSize), avahiError = AVAHI_ERR_INVALID_ADDRESS);
1521 VerifyOrExit(aSize == OTBR_IP6_ADDRESS_SIZE, otbrLogInfo("IPv4 address ignored"),
1522 avahiError = AVAHI_ERR_INVALID_ADDRESS);
1523 address = Ip6Address(*static_cast<const uint8_t(*)[OTBR_IP6_ADDRESS_SIZE]>(aRdata));
1524
1525 VerifyOrExit(!address.IsLinkLocal() && !address.IsMulticast() && !address.IsLoopback() && !address.IsUnspecified(),
1526 avahiError = AVAHI_ERR_INVALID_ADDRESS);
1527 otbrLogInfo("Resolved host address: %s %s", aEvent == AVAHI_BROWSER_NEW ? "add" : "remove",
1528 address.ToString().c_str());
1529
1530 mHostInfo.mHostName = std::string(aName) + ".";
1531 if (aEvent == AVAHI_BROWSER_NEW)
1532 {
1533 mHostInfo.AddAddress(address);
1534 }
1535 else
1536 {
1537 mHostInfo.RemoveAddress(address);
1538 }
1539 mHostInfo.mNetifIndex = static_cast<uint32_t>(aInterfaceIndex);
1540 // TODO: Use a more proper TTL
1541 mHostInfo.mTtl = kDefaultTtl;
1542 resolved = true;
1543
1544 exit:
1545 if (resolved)
1546 {
1547 // NOTE: This `HostSubscrption` object may be freed in `OnHostResolved`.
1548 mPublisherAvahi->OnHostResolved(mHostName, mHostInfo);
1549 }
1550 else if (avahiError != AVAHI_OK)
1551 {
1552 mPublisherAvahi->OnHostResolveFailed(mHostName, avahiError);
1553 }
1554 }
1555
1556 } // namespace Mdns
1557
1558 } // namespace otbr
1559