Fawkes API Fawkes Development Version
avahi_thread.cpp
1
2/***************************************************************************
3 * avahi_thread.cpp - Avahi thread
4 *
5 * Created: Wed Nov 08 11:19:25 2006
6 * Copyright 2006-2011 Tim Niemueller [www.niemueller.de]
7 *
8 ****************************************************************************/
9
10/* This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version. A runtime exception applies to
14 * this software (see LICENSE.GPL_WRE file mentioned below for details).
15 *
16 * This program is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 * GNU Library General Public License for more details.
20 *
21 * Read the full text in the LICENSE.GPL_WRE file in the doc directory.
22 */
23
24#include <arpa/inet.h>
25#include <avahi-client/lookup.h>
26#include <avahi-client/publish.h>
27#include <avahi-common/alternative.h>
28#include <avahi-common/error.h>
29#include <avahi-common/malloc.h>
30#include <avahi-common/simple-watch.h>
31#include <avahi-common/timeval.h>
32#include <core/exceptions/software.h>
33#include <core/threading/mutex.h>
34#include <core/threading/wait_condition.h>
35#include <net/if.h>
36#include <netcomm/dns-sd/avahi_resolver_handler.h>
37#include <netcomm/dns-sd/avahi_thread.h>
38#include <netinet/in.h>
39#include <sys/socket.h>
40#include <sys/types.h>
41#include <utils/misc/string_conversions.h>
42
43#include <cstddef>
44#include <cstdio>
45#include <cstdlib>
46#include <cstring>
47#include <netdb.h>
48#include <thread>
49
50namespace fawkes {
51
52/** @class AvahiThread netcomm/dns-sd/avahi_thread.h
53 * Avahi main thread.
54 * This thread handles all tasks related to avahi. This is the single
55 * interaction point with the Avahi adapter.
56 *
57 * @ingroup NetComm
58 * @author Tim Niemueller
59 */
60
61/** Time to wait if creating an avahi client fails. **/
62const std::chrono::seconds AvahiThread::wait_on_init_failure{5};
63
64/** Constructor.
65 * You can choose whether to announce IPv4 or IPv6 only or both.
66 * If you select both, new service will be created with the "unspecified"
67 * address family in Avahi, causing it to announce the service on all
68 * supported protocols (which may or may not include both).
69 * @param enable_ipv4 enable IPv4 support
70 * @param enable_ipv6 enable IPv6 support
71 */
72AvahiThread::AvahiThread(bool enable_ipv4, bool enable_ipv6)
73: Thread("AvahiThread"), enable_ipv4(enable_ipv4), enable_ipv6(enable_ipv6)
74{
75 simple_poll = NULL;
76 client = NULL;
77
78 need_recover = false;
79 do_reset_groups = false;
80
81 if (enable_ipv4 && enable_ipv6) {
82 service_protocol = AVAHI_PROTO_UNSPEC;
83 } else if (enable_ipv4) {
84 service_protocol = AVAHI_PROTO_INET;
85 } else if (enable_ipv6) {
86 service_protocol = AVAHI_PROTO_INET6;
87 } else {
88 throw Exception("Neither IPv4 nor IPv6 enabled");
89 }
90
91 init_wc = new WaitCondition();
92
94}
95
96/** Destructor. */
98{
99 delete init_wc;
100
101 remove_pending_services();
102 remove_pending_browsers();
103
104 erase_groups();
105 erase_browsers();
106
107 if (client)
108 avahi_client_free(client);
109
110 if (simple_poll)
111 avahi_simple_poll_free(simple_poll);
112}
113
114/** Avahi thread loop.
115 * The avahi thread calls the simple poll iterate to poll with an infinite
116 * timeout. This way the loop blocks until an event occurs.
117 */
118void
120{
121 if (need_recover) {
122 if (client) {
123 avahi_client_free(client);
124 client = NULL;
125 }
126
127 if (simple_poll) {
128 avahi_simple_poll_free(simple_poll);
129 simple_poll = NULL;
130 }
131 }
132
133 if (!simple_poll) {
134 // Init
135 int error;
136
137 if ((simple_poll = avahi_simple_poll_new())) {
138 client = avahi_client_new(avahi_simple_poll_get(simple_poll),
139 AVAHI_CLIENT_NO_FAIL,
140 AvahiThread::client_callback,
141 this,
142 &error);
143
144 if (!client) {
145 avahi_simple_poll_free(simple_poll);
146 simple_poll = NULL;
147 }
148 }
149 }
150
151 if (client) {
152 if (do_reset_groups) {
153 reset_groups();
154 recreate_services();
155 }
156 if (need_recover) {
157 erase_groups();
158 erase_browsers();
159 recreate_services();
160 recreate_browsers();
161 }
162 if (client_state == AVAHI_CLIENT_S_RUNNING) {
163 remove_pending_services();
164 remove_pending_browsers();
165 create_pending_services();
166 create_pending_browsers();
167 start_hostname_resolvers();
168 start_address_resolvers();
169 }
170
171 need_recover = false;
172
173 avahi_simple_poll_iterate(simple_poll, -1);
174 } else {
175 // We failed to create a client, e.g., because the daemon is not running.
176 // Wait for a while and try again.
177 std::this_thread::sleep_for(wait_on_init_failure);
178 }
179}
180
181/** Recover froma broken Avahi connection.
182 * This will erase all service browsers and announced service groups
183 * and will try to reconnect in the next loop.
184 */
185void
186AvahiThread::recover()
187{
188 need_recover = true;
189 wake_poller();
190}
191
192void
193AvahiThread::wake_poller()
194{
195 if (simple_poll) {
196 avahi_simple_poll_wakeup(simple_poll);
197 }
198}
199
200/** Called whenever the client or server state changes.
201 * @param c Avahi client
202 * @param state new state
203 * @param instance Instance of AvahiThread that triggered the event.
204 */
205void
206AvahiThread::client_callback(AvahiClient *c, AvahiClientState state, void *instance)
207{
208 AvahiThread *at = static_cast<AvahiThread *>(instance);
209 at->client_state = state;
210
211 switch (state) {
212 case AVAHI_CLIENT_S_RUNNING:
213 /* The server has startup successfully and registered its host
214 * name on the network, so it's time to create our services */
215 //printf("(Client): RUNNING\n");
216 //at->create_browsers();
217 //at->set_available( true );
218 at->init_done();
219 break;
220
221 case AVAHI_CLIENT_S_COLLISION:
222 //printf("(Client): COLLISION\n");
223 /* Let's drop our registered services. When the server is back
224 * in AVAHI_SERVER_RUNNING state we will register them
225 * again with the new host name. */
226 at->do_reset_groups = true;
227 break;
228
229 case AVAHI_CLIENT_FAILURE:
230 // Doh!
231 //printf("(Client): FAILURE\n");
232 at->recover();
233 break;
234
235 case AVAHI_CLIENT_CONNECTING:
236 //printf("(Client): CONNECTING\n");
237 break;
238
239 case AVAHI_CLIENT_S_REGISTERING:
240 // Ignored
241 //printf("(Client): REGISTERING\n");
242 break;
243 }
244}
245
246/* **********************************************************************************
247 * Avahi Service Publisher methods
248 * **********************************************************************************/
249
250/** Publish service.
251 * @param service service to publish.
252 */
253void
255{
256 if (services_.find(service) == services_.end()) {
257 pending_services_.push_locked(service);
258 } else {
259 throw Exception("Service already registered");
260 }
261
262 wake_poller();
263}
264
265void
267{
268 if (services_.find(*service) != services_.end()) {
269 pending_remove_services_.push_locked(service);
270 } else {
271 throw Exception("Service not registered");
272 }
273
274 wake_poller();
275}
276
277/** Create services. */
278AvahiEntryGroup *
279AvahiThread::create_service(const NetworkService &service, AvahiEntryGroup *exgroup)
280{
281 // the following errors are non-fatal, they can happen since Avahi is started
282 // asynchronously, just ignore them by bailing out
283 if (!client)
284 return NULL;
285
286 AvahiEntryGroup *group;
287 if (exgroup) {
288 group = exgroup;
289 } else {
290 if (!(group = avahi_entry_group_new(client, AvahiThread::entry_group_callback, this))) {
291 throw NullPointerException("Cannot create service group");
292 }
293 }
294
295 AvahiStringList * al = NULL;
296 const std::list<std::string> &l = service.txt();
297 for (std::list<std::string>::const_iterator j = l.begin(); j != l.end(); ++j) {
298 al = avahi_string_list_add(al, j->c_str());
299 }
300
301 int rv = AVAHI_ERR_COLLISION;
302 std::string name = service.modified_name() ? service.modified_name() : service.name();
303 for (int i = 1; (i <= 100) && (rv == AVAHI_ERR_COLLISION); ++i) {
304 rv = avahi_entry_group_add_service_strlst(group,
305 AVAHI_IF_UNSPEC,
306 service_protocol,
307 (AvahiPublishFlags)0,
308 name.c_str(),
309 service.type(),
310 service.domain(),
311 service.host(),
312 service.port(),
313 al);
314
315 if (rv == AVAHI_ERR_COLLISION) {
316 char *n = avahi_alternative_service_name(name.c_str());
317 service.set_modified_name(n);
318 name = n;
319 avahi_free(n);
320 }
321 }
322
323 avahi_string_list_free(al);
324
325 if (rv < 0) {
326 throw Exception("Adding Avahi/mDNS-SD service failed: %s", avahi_strerror(rv));
327 }
328
329 /*
330 if (service.modified_name() != 0) {
331 LibLogger::log_warn("FawkesNetworkManager", "Network service name collision, "
332 "modified to '%s' (from '%s')", service.modified_name(),
333 service.name());
334 }
335 */
336
337 /* Tell the server to register the service */
338 if (avahi_entry_group_commit(group) < 0) {
339 throw Exception("Registering Avahi services failed");
340 }
341
342 return group;
343}
344
345void
346AvahiThread::recreate_services()
347{
348 for (sit_ = services_.begin(); sit_ != services_.end(); ++sit_) {
349 (*sit_).second = create_service(sit_->first, sit_->second);
350 }
351}
352
353void
354AvahiThread::create_pending_services()
355{
356 pending_services_.lock();
357 while (!pending_services_.empty()) {
358 NetworkService &s = pending_services_.front();
359 services_[s] = create_service(s, NULL);
360 pending_services_.pop();
361 }
362 pending_services_.unlock();
363}
364
365void
366AvahiThread::remove_pending_services()
367{
368 Thread::CancelState old_state;
370 pending_remove_services_.lock();
371 while (!pending_remove_services_.empty()) {
372 NetworkService &s = pending_remove_services_.front();
373 if (services_.find(s) != services_.end()) {
374 group_erase(services_[s]);
375 services_.erase_locked(s);
376 }
377 pending_remove_services_.pop();
378 }
379 pending_remove_services_.unlock();
380 set_cancel_state(old_state);
381}
382
383/** Drop our registered services.
384 * When the server is back in AVAHI_SERVER_RUNNING state we will register them
385 * again with the new host name (triggered by AvahiThread).
386 */
387void
388AvahiThread::group_reset(AvahiEntryGroup *g)
389{
390 if (g) {
391 avahi_entry_group_reset(g);
392 }
393}
394
395/** Erase service group. */
396void
397AvahiThread::group_erase(AvahiEntryGroup *g)
398{
399 if (g) {
400 avahi_entry_group_reset(g);
401 avahi_entry_group_free(g);
402 }
403}
404
405void
406AvahiThread::erase_groups()
407{
408 for (sit_ = services_.begin(); sit_ != services_.end(); ++sit_) {
409 if (sit_->second)
410 group_erase(sit_->second);
411 sit_->second = NULL;
412 }
413}
414
415void
416AvahiThread::reset_groups()
417{
418 for (sit_ = services_.begin(); sit_ != services_.end(); ++sit_) {
419 group_reset((*sit_).second);
420 }
421}
422
423/** Called if there was a name collision. */
424void
425AvahiThread::name_collision(AvahiEntryGroup *g)
426{
427 for (sit_ = services_.begin(); sit_ != services_.end(); ++sit_) {
428 if ((*sit_).second == g) {
429 NetworkService service = sit_->first;
430 std::string name = service.modified_name() ? service.modified_name() : service.name();
431
432 /* A service name collision happened. Let's pick a new name */
433 char *n = avahi_alternative_service_name((*sit_).first.name());
434 service.set_modified_name(n);
435 avahi_free(n);
436
437 pending_remove_services_.push_locked(service);
438 pending_services_.push_locked(service);
439 }
440 }
441}
442
443/** Callback for Avahi.
444 * @param g entry group
445 * @param state new state
446 * @param instance instance of AvahiThread that triggered the event.
447 */
448void
449AvahiThread::entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, void *instance)
450{
451 AvahiThread *at = static_cast<AvahiThread *>(instance);
452
453 switch (state) {
454 case AVAHI_ENTRY_GROUP_ESTABLISHED:
455 /* The entry group has been established successfully */
456 //fprintf(stderr, "Service '%s' successfully established.\n", name);
457 break;
458
459 case AVAHI_ENTRY_GROUP_COLLISION: {
460 at->name_collision(g);
461 break;
462 }
463
464 case AVAHI_ENTRY_GROUP_FAILURE:
465 /* Some kind of failure happened while we were registering our services */
466 at->recover();
467 break;
468
469 case AVAHI_ENTRY_GROUP_UNCOMMITED:
470 case AVAHI_ENTRY_GROUP_REGISTERING: break;
471 }
472}
473
474/* **********************************************************************************
475 * Avahi Browser Publisher methods
476 * **********************************************************************************/
477
478/** Add a result handler.
479 * A handler is added for the given service type. A search is initiated
480 * for the given service and the given handler is called for added or
481 * removed services or if an error occurs.
482 * @param service_type string of the service type
483 * @param h The ServiceBrowseHandler
484 */
485void
487{
488 handlers_[service_type].push_back(h);
489 pending_browsers_.push_locked(service_type);
490
491 wake_poller();
492}
493
494/** Remove a handler.
495 * The handler is removed and no further events will be emitted to the
496 * handler.
497 * @param service_type service type to de-register the handler for
498 * @param h the handler
499 */
500void
502{
503 if (handlers_.find(service_type) != handlers_.end()) {
504 handlers_[service_type].remove(h);
505 if (handlers_[service_type].size() == 0) {
506 if (browsers_.find(service_type) != browsers_.end()) {
507 pending_browser_removes_.push_locked(service_type);
508 //avahi_service_browser_free(browsers_[service_type]);
509 //browsers_.erase(service_type);
510 }
511 handlers_.erase(service_type);
512 }
513 }
514
515 wake_poller();
516}
517
518/** Create browser for a given service.
519 * @param service_type service type
520 */
521void
522AvahiThread::create_browser(const char *service_type)
523{
524 if (browsers_.find(service_type) == browsers_.end()) {
525 if (client) {
526 AvahiServiceBrowser *b = avahi_service_browser_new(client,
527 AVAHI_IF_UNSPEC,
528 service_protocol,
529 service_type,
530 NULL,
531 (AvahiLookupFlags)0,
532 AvahiThread::browse_callback,
533 this);
534
535 if (!b) {
536 handlers_[service_type].pop_back();
537 throw NullPointerException("Could not instantiate AvahiServiceBrowser");
538 }
539 browsers_[service_type] = b;
540 }
541 }
542}
543
544/** Create browsers.
545 * Creates browser for all services.
546 */
547void
548AvahiThread::recreate_browsers()
549{
550 LockMap<std::string, std::list<ServiceBrowseHandler *>>::iterator i;
551 for (i = handlers_.begin(); i != handlers_.end(); ++i) {
552 create_browser((*i).first.c_str());
553 }
554}
555
556void
557AvahiThread::create_pending_browsers()
558{
559 pending_browsers_.lock();
560 while (!pending_browsers_.empty()) {
561 //printf("Creating browser for %s\n", pending_browsers_.front().c_str());
562 create_browser(pending_browsers_.front().c_str());
563 pending_browsers_.pop();
564 }
565 pending_browsers_.unlock();
566}
567
568void
569AvahiThread::remove_pending_browsers()
570{
571 Thread::CancelState old_state;
573 pending_browser_removes_.lock();
574 while (!pending_browser_removes_.empty()) {
575 std::string &s = pending_browser_removes_.front();
576 avahi_service_browser_free(browsers_[s]);
577 browsers_.erase_locked(s);
578 pending_browser_removes_.pop();
579 }
580 pending_browser_removes_.unlock();
581 set_cancel_state(old_state);
582}
583
584/** Erase all browsers. */
585void
586AvahiThread::erase_browsers()
587{
588 std::map<std::string, AvahiServiceBrowser *>::iterator i;
589 for (i = browsers_.begin(); i != browsers_.end(); ++i) {
590 avahi_service_browser_free((*i).second);
591 }
592 browsers_.clear();
593}
594
595/** Call handler for a removed service.
596 * @param name name
597 * @param type type
598 * @param domain domain
599 */
600void
601AvahiThread::call_handler_service_removed(const char *name, const char *type, const char *domain)
602{
603 if (handlers_.find(type) != handlers_.end()) {
604 std::list<ServiceBrowseHandler *>::iterator i;
605 for (i = handlers_[type].begin(); i != handlers_[type].end(); ++i) {
606 (*i)->service_removed(name, type, domain);
607 }
608 }
609}
610
611/** Call handler for an added service.
612 * @param name name
613 * @param type type
614 * @param domain domain
615 * @param host_name host name
616 * @param address address of host
617 * @param port port of service
618 * @þaram txt list of TXT records
619 * @param flags flags
620 */
621void
622AvahiThread::call_handler_service_added(const char * name,
623 const char * type,
624 const char * domain,
625 const char * host_name,
626 const AvahiIfIndex interface,
627 const AvahiAddress * address,
628 uint16_t port,
629 std::list<std::string> &txt,
630 AvahiLookupResultFlags flags)
631{
632 char ifname[IF_NAMESIZE];
633 ifname[0] = 0;
634 if (if_indextoname(interface, ifname) == NULL) {
635 fprintf(stderr, "AvahiThread::call_handler_service_added: IPv6 if_indextoname failed");
636 return;
637 }
638
639 struct sockaddr *s = NULL;
640 socklen_t slen;
641 if (address->proto == AVAHI_PROTO_INET) {
642 if (!enable_ipv4)
643 return;
644 slen = sizeof(struct sockaddr_in);
645 struct sockaddr_in *sin = (struct sockaddr_in *)malloc(slen);
646 sin->sin_family = AF_INET;
647 sin->sin_addr.s_addr = address->data.ipv4.address;
648 sin->sin_port = htons(port);
649 s = (struct sockaddr *)sin;
650 } else if (address->proto == AVAHI_PROTO_INET6) {
651 if (!enable_ipv6)
652 return;
653 slen = sizeof(struct sockaddr_in6);
654 struct sockaddr_in6 *sin = (struct sockaddr_in6 *)malloc(slen);
655 sin->sin6_family = AF_INET6;
656 memcpy(&sin->sin6_addr, &address->data.ipv6.address, sizeof(in6_addr));
657
658 char ipaddr[INET6_ADDRSTRLEN];
659 if (inet_ntop(AF_INET6, &sin->sin6_addr, ipaddr, sizeof(ipaddr)) != NULL) {
660 std::string addr_with_scope = std::string(ipaddr) + "%" + ifname;
661 std::string port_s = StringConversions::to_string((unsigned int)port);
662
663 // use getaddrinfo to fill especially to determine scope ID
664 struct addrinfo hints, *res;
665 memset(&hints, 0, sizeof(hints));
666 hints.ai_family = AF_INET6;
667 hints.ai_flags = AI_NUMERICHOST;
668 if (getaddrinfo(addr_with_scope.c_str(), port_s.c_str(), &hints, &res) == 0) {
669 if (slen == res[0].ai_addrlen) {
670 memcpy(sin, res[0].ai_addr, slen);
671 freeaddrinfo(res);
672 } else {
673 fprintf(stderr,
674 "AvahiThread::call_handler_service_added: IPv6 address lengths different");
675 freeaddrinfo(res);
676 return;
677 }
678 } else {
679 fprintf(stderr, "AvahiThread::call_handler_service_added: IPv6 getaddrinfo failed");
680 return;
681 }
682 } else {
683 fprintf(stderr, "AvahiThread::call_handler_service_added: IPv6 inet_ntop failed");
684 return;
685 }
686 s = (struct sockaddr *)sin;
687 } else {
688 // ignore
689 return;
690 }
691 if (handlers_.find(type) != handlers_.end()) {
692 std::list<ServiceBrowseHandler *>::iterator i;
693 for (i = handlers_[type].begin(); i != handlers_[type].end(); ++i) {
694 (*i)->service_added(
695 name, type, domain, host_name, ifname, (struct sockaddr *)s, slen, port, txt, (int)flags);
696 }
697 }
698 free(s);
699}
700
701/** Call handler for failure.
702 * @param name name
703 * @param type type
704 * @param domain domain
705 */
706void
707AvahiThread::call_handler_failed(const char *name, const char *type, const char *domain)
708{
709 if (handlers_.find(type) != handlers_.end()) {
710 std::list<ServiceBrowseHandler *>::iterator i;
711 for (i = handlers_[type].begin(); i != handlers_[type].end(); ++i) {
712 (*i)->browse_failed(name, type, domain);
713 }
714 }
715}
716
717/** Call handler "all for now".
718 * @param type type
719 */
720void
721AvahiThread::call_handler_all_for_now(const char *type)
722{
723 if (handlers_.find(type) != handlers_.end()) {
724 std::list<ServiceBrowseHandler *>::iterator i;
725 for (i = handlers_[type].begin(); i != handlers_[type].end(); ++i) {
726 (*i)->all_for_now();
727 }
728 }
729}
730
731/** Call handler "cache exhausted".
732 * @param type type
733 */
734void
735AvahiThread::call_handler_cache_exhausted(const char *type)
736{
737 if (handlers_.find(type) != handlers_.end()) {
738 std::list<ServiceBrowseHandler *>::iterator i;
739 for (i = handlers_[type].begin(); i != handlers_[type].end(); ++i) {
740 (*i)->cache_exhausted();
741 }
742 }
743}
744
745/** Callback for Avahi.
746 * Callback called by Avahi.
747 * @param b service browser
748 * @param interface interface index
749 * @param protocol protocol
750 * @param event event
751 * @param name name
752 * @param type type
753 * @param domain domain
754 * @param flags flags
755 * @param instance pointer to the AvahiThread instance that initiated
756 * the search
757 */
758void
759AvahiThread::browse_callback(AvahiServiceBrowser * b,
760 AvahiIfIndex interface,
761 AvahiProtocol protocol,
762 AvahiBrowserEvent event,
763 const char * name,
764 const char * type,
765 const char * domain,
766 AvahiLookupResultFlags flags,
767 void * instance)
768{
769 AvahiThread *at = static_cast<AvahiThread *>(instance);
770
771 switch (event) {
772 case AVAHI_BROWSER_FAILURE:
773 //printf("(Browser) %s\n", avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b))));
774 return;
775
776 case AVAHI_BROWSER_NEW:
777 //printf("(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
778 // We ignore the returned resolver object. In the callback
779 // function we free it. If the server is terminated before
780 // the callback function is called the server will free
781 // the resolver for us.
782 if (!(avahi_service_resolver_new(at->client,
783 interface,
784 protocol,
785 name,
786 type,
787 domain,
788 protocol,
789 (AvahiLookupFlags)0,
790 AvahiThread::resolve_callback,
791 instance))) {
792 throw NullPointerException("Could not instantiate resolver");
793 }
794 break;
795
796 case AVAHI_BROWSER_REMOVE:
797 // handler
798 //printf("(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
799 at->call_handler_service_removed(name, type, domain);
800 break;
801
802 case AVAHI_BROWSER_ALL_FOR_NOW:
803 // handler
804 //printf("(Browser) ALL_FOR_NOW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
805 at->call_handler_all_for_now(type);
806 break;
807
808 case AVAHI_BROWSER_CACHE_EXHAUSTED:
809 // handler
810 //printf("(Browser) CACHE_EXHAUSTED: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
811 at->call_handler_cache_exhausted(type);
812 break;
813 }
814}
815
816/** Callback for Avahi.
817 * Callback called by Avahi.
818 * @param r service resolver
819 * @param interface interface index
820 * @param protocol protocol
821 * @param event event
822 * @param name name
823 * @param type type
824 * @param domain domain
825 * @param host_name host name
826 * @param address address
827 * @param port port
828 * @param txt TXT records
829 * @param flags flags
830 * @param instance pointer to the AvahiThread instance that initiated
831 * the search
832 */
833void
834AvahiThread::resolve_callback(AvahiServiceResolver * r,
835 AvahiIfIndex interface,
836 AVAHI_GCC_UNUSED AvahiProtocol protocol,
837 AvahiResolverEvent event,
838 const char * name,
839 const char * type,
840 const char * domain,
841 const char * host_name,
842 const AvahiAddress * address,
843 uint16_t port,
844 AvahiStringList * txt,
845 AvahiLookupResultFlags flags,
846 void * instance)
847{
848 AvahiThread *at = static_cast<AvahiThread *>(instance);
849
850 switch (event) {
851 case AVAHI_RESOLVER_FAILURE:
852 // handler failure
853 at->call_handler_failed(name, type, domain);
854 break;
855
856 case AVAHI_RESOLVER_FOUND:
857 // handler add
858 {
859 std::list<std::string> txts;
860 AvahiStringList * l = txt;
861
862 txts.clear();
863 while (l) {
864 txts.push_back((char *)avahi_string_list_get_text(l));
865 l = avahi_string_list_get_next(l);
866 }
867
868 at->call_handler_service_added(
869 name, type, domain, host_name, interface, address, port, txts, flags);
870 }
871 break;
872 }
873
874 avahi_service_resolver_free(r);
875}
876
877/* **********************************************************************************
878 * Avahi resolver methods
879 * **********************************************************************************/
880
881/** Order name resolution.
882 * This initiates resolution of a name. The method immediately returns and will not
883 * wait for the result.
884 * @param name name to resolve.
885 * @param handler handler to call for the result
886 */
887void
889{
890 AvahiResolverCallbackData *data = new AvahiResolverCallbackData(this, handler);
891
892 if (pending_hostname_resolves_.find(name) == pending_hostname_resolves_.end()) {
893 pending_hostname_resolves_[name] = data;
894 }
895
896 wake_poller();
897}
898
899void
900AvahiThread::start_hostname_resolver(const char *name, AvahiResolverCallbackData *data)
901{
902 AvahiHostNameResolver *resolver;
903 if ((resolver = avahi_host_name_resolver_new(client,
904 AVAHI_IF_UNSPEC,
905 AVAHI_PROTO_UNSPEC,
906 name,
907 service_protocol,
908 AVAHI_LOOKUP_USE_MULTICAST,
909 AvahiThread::host_name_resolver_callback,
910 data))
911 == NULL) {
912 throw Exception("Cannot create Avahi name resolver");
913 } else {
914 running_hostname_resolvers_.push_back(resolver);
915 }
916}
917
918void
919AvahiThread::start_hostname_resolvers()
920{
921 LockMap<std::string, AvahiResolverCallbackData *>::iterator phrit;
922 for (phrit = pending_hostname_resolves_.begin(); phrit != pending_hostname_resolves_.end();
923 ++phrit) {
924 start_hostname_resolver(phrit->first.c_str(), phrit->second);
925 }
926 pending_hostname_resolves_.clear();
927}
928
929void
930AvahiThread::start_address_resolvers()
931{
932 LockMap<struct ::sockaddr_storage *, AvahiResolverCallbackData *>::iterator parit;
933
934 for (parit = pending_address_resolves_.begin(); parit != pending_address_resolves_.end();
935 ++parit) {
936 start_address_resolver(parit->first, parit->second);
937 free(parit->first);
938 }
939 pending_address_resolves_.clear();
940}
941
942/** Order address resolution.
943 * This initiates resolution of an address. The method immediately returns and will not
944 * wait for the result.
945 * @param addr address to resolve
946 * @param addrlen length of addr in bytes
947 * @param handler handler to call for the result
948 */
949void
950AvahiThread::resolve_address(struct sockaddr * addr,
951 socklen_t addrlen,
952 AvahiResolverHandler *handler)
953{
954 struct ::sockaddr_storage *sstor =
955 (struct ::sockaddr_storage *)malloc(sizeof(struct ::sockaddr_storage));
956 if (addr->sa_family == AF_INET) {
957 if (addrlen != sizeof(sockaddr_in)) {
958 throw Exception("Invalid size for IPv4 address struct");
959 }
960 memcpy(sstor, addr, sizeof(sockaddr_in));
961 } else if (addr->sa_family == AF_INET6) {
962 if (addrlen != sizeof(sockaddr_in6)) {
963 throw Exception("Invalid size for IPv6 address struct");
964 }
965 memcpy(sstor, addr, sizeof(sockaddr_in6));
966 } else {
967 throw Exception("Unknown address family");
968 }
969 AvahiResolverCallbackData *data = new AvahiResolverCallbackData(this, handler);
970
971 pending_address_resolves_[sstor] = data;
972 wake_poller();
973}
974
975void
976AvahiThread::start_address_resolver(const struct sockaddr_storage *in_addr,
977 AvahiResolverCallbackData * data)
978{
979 AvahiAddress a;
980
981 if (in_addr->ss_family == AF_INET) {
982 a.proto = AVAHI_PROTO_INET;
983 a.data.ipv4.address = ((sockaddr_in *)in_addr)->sin_addr.s_addr;
984 } else if (in_addr->ss_family == AF_INET6) {
985 a.proto = AVAHI_PROTO_INET6;
986 memcpy(&a.data.ipv6.address, &((sockaddr_in6 *)in_addr)->sin6_addr, sizeof(in6_addr));
987 } else {
988 throw Exception("Unknown address family");
989 }
990
991 AvahiAddressResolver *resolver;
992 if ((resolver = avahi_address_resolver_new(client,
993 AVAHI_IF_UNSPEC,
994 AVAHI_PROTO_UNSPEC,
995 &a,
996 AVAHI_LOOKUP_USE_MULTICAST,
997 AvahiThread::address_resolver_callback,
998 data))
999 == NULL) {
1000 Exception e("Cannot create Avahi address resolver");
1001 e.append("Avahi error: %s", avahi_strerror(avahi_client_errno(client)));
1002 throw e;
1003 } else {
1004 running_address_resolvers_.push_back_locked(resolver);
1005 }
1006}
1007
1008/** Remove hostname resolver.
1009 * Used internally by callback.
1010 * @param r resolver
1011 */
1012void
1013AvahiThread::remove_hostname_resolver(AvahiHostNameResolver *r)
1014{
1015 running_hostname_resolvers_.remove_locked(r);
1016}
1017
1018/** Remove address resolver.
1019 * Used internally by callback.
1020 * @param r resolver
1021 */
1022void
1023AvahiThread::remove_address_resolver(AvahiAddressResolver *r)
1024{
1025 running_address_resolvers_.remove_locked(r);
1026}
1027
1028/** Internal callback.
1029 * Callback for avahi.
1030 */
1031void
1032AvahiThread::host_name_resolver_callback(AvahiHostNameResolver *r,
1033 AvahiIfIndex interface,
1034 AvahiProtocol protocol,
1035 AvahiResolverEvent event,
1036 const char * name,
1037 const AvahiAddress * a,
1038 AvahiLookupResultFlags flags,
1039 void * userdata)
1040{
1041 AvahiResolverCallbackData *cd = static_cast<AvahiResolverCallbackData *>(userdata);
1042
1043 cd->first->remove_hostname_resolver(r);
1044 avahi_host_name_resolver_free(r);
1045
1046 switch (event) {
1047 case AVAHI_RESOLVER_FOUND: {
1048 if (protocol == AVAHI_PROTO_INET) {
1049 struct sockaddr_in *res = (struct sockaddr_in *)malloc(sizeof(struct sockaddr_in));
1050 res->sin_family = (unsigned short)avahi_proto_to_af(protocol);
1051 res->sin_addr.s_addr = a->data.ipv4.address;
1052 cd->second->resolved_name(strdup(name), (struct sockaddr *)res, sizeof(struct sockaddr_in));
1053 } else if (protocol == AVAHI_PROTO_INET6) {
1054 struct sockaddr_in6 *res = (struct sockaddr_in6 *)malloc(sizeof(struct sockaddr_in6));
1055 res->sin6_family = (unsigned short)avahi_proto_to_af(protocol);
1056 memcpy(&res->sin6_addr, &a->data.ipv6.address, sizeof(in6_addr));
1057 cd->second->resolved_name(strdup(name), (struct sockaddr *)res, sizeof(struct sockaddr_in6));
1058 } else { // don't know
1059 cd->second->name_resolution_failed(strdup(name));
1060 }
1061 } break;
1062
1063 case AVAHI_RESOLVER_FAILURE:
1064 default: cd->second->name_resolution_failed(strdup(name)); break;
1065 }
1066
1067 delete cd;
1068}
1069
1070/** Internal callback.
1071 * Callback for avahi.
1072 */
1073void
1074AvahiThread::address_resolver_callback(AvahiAddressResolver * r,
1075 AvahiIfIndex interface,
1076 AvahiProtocol protocol,
1077 AvahiResolverEvent event,
1078 const AvahiAddress * a,
1079 const char * name,
1080 AvahiLookupResultFlags flags,
1081 void * userdata)
1082{
1083 AvahiResolverCallbackData *cd = static_cast<AvahiResolverCallbackData *>(userdata);
1084
1085 cd->first->remove_address_resolver(r);
1086 avahi_address_resolver_free(r);
1087
1088 struct sockaddr *res = NULL;
1089 socklen_t res_size = 0;
1090
1091 if (protocol == AVAHI_PROTO_INET) {
1092 res_size = sizeof(struct sockaddr_in);
1093 res = (struct sockaddr *)malloc(res_size);
1094 sockaddr_in *res_4 = (struct sockaddr_in *)res;
1095 res_4->sin_family = (unsigned short)avahi_proto_to_af(protocol);
1096 res_4->sin_addr.s_addr = a->data.ipv4.address;
1097 } else if (protocol == AVAHI_PROTO_INET6) {
1098 res_size = sizeof(struct sockaddr_in6);
1099 res = (struct sockaddr *)malloc(res_size);
1100 sockaddr_in6 *res_6 = (struct sockaddr_in6 *)res;
1101 res_6->sin6_family = (unsigned short)avahi_proto_to_af(protocol);
1102 memcpy(&res_6->sin6_addr, &a->data.ipv6.address, sizeof(in6_addr));
1103 }
1104
1105 switch (event) {
1106 case AVAHI_RESOLVER_FOUND: cd->second->resolved_address(res, res_size, strdup(name)); break;
1107 case AVAHI_RESOLVER_FAILURE: cd->second->address_resolution_failed(res, res_size); break;
1108
1109 default: cd->second->address_resolution_failed(NULL, 0); break;
1110 }
1111
1112 delete cd;
1113}
1114
1115/** Unlocks init lock.
1116 * Only to be called by client_callback().
1117 */
1118void
1119AvahiThread::init_done()
1120{
1121 wake_poller();
1122 init_wc->wake_all();
1123}
1124
1125/** Waits for the AvahiThread to be initialized.
1126 * You can use this if you want to wait until the thread has been
1127 * fully initialized and may be used. Since the happens in this thread
1128 * it is in general not immediately ready after start().
1129 * This will block the calling thread until the AvahiThread has
1130 * been initialized. This is done by waiting for a release of an
1131 * initialization mutex.
1132 */
1133void
1135{
1136 init_wc->wait();
1137}
1138
1139} // end namespace fawkes
Avahi resolver handler interface.
void resolve_name(const char *name, AvahiResolverHandler *handler)
Order name resolution.
void watch_service(const char *service_type, ServiceBrowseHandler *h)
Add a result handler.
~AvahiThread()
Destructor.
void publish_service(NetworkService *service)
Publish service.
void unwatch_service(const char *service_type, ServiceBrowseHandler *h)
Remove a handler.
void unpublish_service(NetworkService *service)
Revoke service publication.
AvahiThread(bool enable_ipv4=true, bool enable_ipv6=true)
Constructor.
void wait_initialized()
Waits for the AvahiThread to be initialized.
virtual void loop()
Avahi thread loop.
void resolve_address(struct sockaddr *addr, socklen_t addrlen, AvahiResolverHandler *handler)
Order address resolution.
Base class for exceptions in Fawkes.
Definition: exception.h:36
void push_back_locked(const Type &x)
Push element to list at back with lock protection.
Definition: lock_list.h:145
void remove_locked(const Type &x)
Remove element from list with lock protection.
Definition: lock_list.h:163
void erase_locked(const KeyType &key)
Remove item with lock.
Definition: lock_map.h:120
void push_locked(const Type &x)
Push element to queue with lock protection.
Definition: lock_queue.h:135
void lock() const
Lock queue.
Definition: lock_queue.h:114
void unlock() const
Unlock list.
Definition: lock_queue.h:128
Representation of a service announced or found via service discovery (i.e.
Definition: service.h:38
void set_modified_name(const char *new_name) const
Set modified name of service.
Definition: service.cpp:360
const char * modified_name() const
Get modified name of service.
Definition: service.cpp:374
const char * type() const
Get type of service.
Definition: service.cpp:383
unsigned short int port() const
Get port of service.
Definition: service.cpp:410
const char * name() const
Get name of service.
Definition: service.cpp:349
const char * host() const
Get host of service.
Definition: service.cpp:401
const char * domain() const
Get domain of service.
Definition: service.cpp:392
const std::list< std::string > & txt() const
Get TXT record list of service.
Definition: service.cpp:447
A NULL pointer was supplied where not allowed.
Definition: software.h:32
Interface for class that process browse results.
static std::string to_string(unsigned int i)
Convert unsigned int value to a string.
Thread class encapsulation of pthreads.
Definition: thread.h:46
void set_prepfin_conc_loop(bool concurrent=true)
Set concurrent execution of prepare_finalize() and loop().
Definition: thread.cpp:716
const char * name() const
Get name of thread.
Definition: thread.h:100
CancelState
Cancel state.
Definition: thread.h:64
@ CANCEL_DISABLED
thread cannot be cancelled
Definition: thread.h:66
static void set_cancel_state(CancelState new_state, CancelState *old_state=0)
Set the cancel state of the current thread.
Definition: thread.cpp:1396
Wait until a given condition holds.
void wait()
Wait for the condition forever.
void wake_all()
Wake up all waiting threads.
Fawkes library namespace.