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