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