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 
50 namespace 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. **/
62 const 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  */
72 AvahiThread::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  */
118 void
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  */
185 void
186 AvahiThread::recover()
187 {
188  need_recover = true;
189  wake_poller();
190 }
191 
192 void
193 AvahiThread::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  */
205 void
206 AvahiThread::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  */
253 void
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 
265 void
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. */
278 AvahiEntryGroup *
279 AvahiThread::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 
345 void
346 AvahiThread::recreate_services()
347 {
348  for (sit_ = services_.begin(); sit_ != services_.end(); ++sit_) {
349  (*sit_).second = create_service(sit_->first, sit_->second);
350  }
351 }
352 
353 void
354 AvahiThread::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 
365 void
366 AvahiThread::remove_pending_services()
367 {
368  Thread::CancelState old_state;
369  set_cancel_state(CANCEL_DISABLED, &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  */
387 void
388 AvahiThread::group_reset(AvahiEntryGroup *g)
389 {
390  if (g) {
391  avahi_entry_group_reset(g);
392  }
393 }
394 
395 /** Erase service group. */
396 void
397 AvahiThread::group_erase(AvahiEntryGroup *g)
398 {
399  if (g) {
400  avahi_entry_group_reset(g);
401  avahi_entry_group_free(g);
402  }
403 }
404 
405 void
406 AvahiThread::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 
415 void
416 AvahiThread::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. */
424 void
425 AvahiThread::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  */
448 void
449 AvahiThread::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  */
485 void
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  */
500 void
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  */
521 void
522 AvahiThread::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  */
547 void
548 AvahiThread::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 
556 void
557 AvahiThread::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 
568 void
569 AvahiThread::remove_pending_browsers()
570 {
571  Thread::CancelState old_state;
572  set_cancel_state(CANCEL_DISABLED, &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. */
585 void
586 AvahiThread::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  */
600 void
601 AvahiThread::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  */
621 void
622 AvahiThread::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  */
706 void
707 AvahiThread::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  */
720 void
721 AvahiThread::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  */
734 void
735 AvahiThread::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  */
758 void
759 AvahiThread::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  */
833 void
834 AvahiThread::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  */
887 void
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 
899 void
900 AvahiThread::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 
918 void
919 AvahiThread::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 
929 void
930 AvahiThread::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  */
949 void
950 AvahiThread::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 
975 void
976 AvahiThread::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  */
1012 void
1013 AvahiThread::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  */
1022 void
1023 AvahiThread::remove_address_resolver(AvahiAddressResolver *r)
1024 {
1025  running_address_resolvers_.remove_locked(r);
1026 }
1027 
1028 /** Internal callback.
1029  * Callback for avahi.
1030  */
1031 void
1032 AvahiThread::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  */
1073 void
1074 AvahiThread::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  */
1118 void
1119 AvahiThread::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  */
1133 void
1135 {
1136  init_wc->wait();
1137 }
1138 
1139 } // end namespace fawkes
fawkes::NetworkService::set_modified_name
void set_modified_name(const char *new_name) const
Set modified name of service.
Definition: service.cpp:360
fawkes::AvahiThread::resolve_name
void resolve_name(const char *name, AvahiResolverHandler *handler)
Order name resolution.
Definition: avahi_thread.cpp:888
fawkes::Thread::set_prepfin_conc_loop
void set_prepfin_conc_loop(bool concurrent=true)
Set concurrent execution of prepare_finalize() and loop().
Definition: thread.cpp:716
fawkes::LockQueue::push_locked
void push_locked(const Type &x)
Push element to queue with lock protection.
Definition: lock_queue.h:135
fawkes::WaitCondition
Wait until a given condition holds.
Definition: wait_condition.h:37
fawkes::NetworkService::modified_name
const char * modified_name() const
Get modified name of service.
Definition: service.cpp:374
fawkes::AvahiThread::unpublish_service
void unpublish_service(NetworkService *service)
Revoke service publication.
Definition: avahi_thread.cpp:266
fawkes::AvahiThread::watch_service
void watch_service(const char *service_type, ServiceBrowseHandler *h)
Add a result handler.
Definition: avahi_thread.cpp:486
fawkes::NetworkService::domain
const char * domain() const
Get domain of service.
Definition: service.cpp:392
fawkes::NetworkService::txt
const std::list< std::string > & txt() const
Get TXT record list of service.
Definition: service.cpp:447
fawkes::Thread::name
const char * name() const
Get name of thread.
Definition: thread.h:100
fawkes::AvahiThread::~AvahiThread
~AvahiThread()
Destructor.
Definition: avahi_thread.cpp:97
fawkes::AvahiThread::publish_service
void publish_service(NetworkService *service)
Publish service.
Definition: avahi_thread.cpp:254
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:72
fawkes::WaitCondition::wait
void wait()
Wait for the condition forever.
Definition: wait_condition.cpp:139
fawkes::AvahiThread::resolve_address
void resolve_address(struct sockaddr *addr, socklen_t addrlen, AvahiResolverHandler *handler)
Order address resolution.
Definition: avahi_thread.cpp:950
fawkes::NetworkService::host
const char * host() const
Get host of service.
Definition: service.cpp:401
fawkes::NetworkService
Representation of a service announced or found via service discovery (i.e.
Definition: service.h:38
fawkes
Fawkes library namespace.
fawkes::NetworkService::port
unsigned short int port() const
Get port of service.
Definition: service.cpp:410
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:287
fawkes::AvahiResolverHandler
Avahi resolver handler interface.
Definition: avahi_resolver_handler.h:32
fawkes::NetworkService::type
const char * type() const
Get type of service.
Definition: service.cpp:383
fawkes::AvahiThread::wait_initialized
void wait_initialized()
Waits for the AvahiThread to be initialized.
Definition: avahi_thread.cpp:1134
fawkes::LockQueue::lock
void lock() const
Lock queue.
Definition: lock_queue.h:114
fawkes::Thread
Thread class encapsulation of pthreads.
Definition: thread.h:46
fawkes::AvahiThread::unwatch_service
void unwatch_service(const char *service_type, ServiceBrowseHandler *h)
Remove a handler.
Definition: avahi_thread.cpp:501
fawkes::NetworkService::name
const char * name() const
Get name of service.
Definition: service.cpp:349
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:1396
fawkes::LockMap::erase_locked
void erase_locked(const KeyType &key)
Remove item with lock.
Definition: lock_map.h:120
fawkes::LockList::remove_locked
void remove_locked(const Type &x)
Remove element from list with lock protection.
Definition: lock_list.h:163
fawkes::NullPointerException
A NULL pointer was supplied where not allowed.
Definition: software.h:32
fawkes::AvahiThread::loop
virtual void loop()
Avahi thread loop.
Definition: avahi_thread.cpp:119
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:145
fawkes::ServiceBrowseHandler
Interface for class that process browse results.
Definition: browse_handler.h:47
fawkes::LockQueue::unlock
void unlock() const
Unlock list.
Definition: lock_queue.h:128
fawkes::Exception
Base class for exceptions in Fawkes.
Definition: exception.h:36