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