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  freeaddrinfo(res);
687  } else {
688  fprintf(stderr, "AvahiThread::call_handler_service_added: IPv6 address lengths different");
689  freeaddrinfo(res);
690  return;
691  }
692  } else {
693  fprintf(stderr, "AvahiThread::call_handler_service_added: IPv6 getaddrinfo failed");
694  return;
695  }
696  } else {
697  fprintf(stderr, "AvahiThread::call_handler_service_added: IPv6 inet_ntop failed");
698  return;
699  }
700  s = (struct sockaddr *)sin;
701  } else {
702  // ignore
703  return;
704  }
705  if ( __handlers.find(type) != __handlers.end() ) {
706  std::list<ServiceBrowseHandler *>::iterator i;
707  for ( i = __handlers[type].begin(); i != __handlers[type].end(); ++i) {
708  (*i)->service_added(name, type, domain, host_name, ifname,
709  (struct sockaddr *)s, slen, port, txt, (int)flags);
710  }
711  }
712  free(s);
713 }
714 
715 
716 /** Call handler for failure.
717  * @param name name
718  * @param type type
719  * @param domain domain
720  */
721 void
722 AvahiThread::call_handler_failed( const char *name,
723  const char *type,
724  const char *domain)
725 {
726  if ( __handlers.find(type) != __handlers.end() ) {
727  std::list<ServiceBrowseHandler *>::iterator i;
728  for ( i = __handlers[type].begin(); i != __handlers[type].end(); ++i) {
729  (*i)->browse_failed(name, type, domain);
730  }
731  }
732 }
733 
734 
735 /** Call handler "all for now".
736  * @param type type
737  */
738 void
739 AvahiThread::call_handler_all_for_now(const char *type)
740 {
741  if ( __handlers.find(type) != __handlers.end() ) {
742  std::list<ServiceBrowseHandler *>::iterator i;
743  for ( i = __handlers[type].begin(); i != __handlers[type].end(); ++i) {
744  (*i)->all_for_now();
745  }
746  }
747 }
748 
749 
750 /** Call handler "cache exhausted".
751  * @param type type
752  */
753 void
754 AvahiThread::call_handler_cache_exhausted(const char *type)
755 {
756  if ( __handlers.find(type) != __handlers.end() ) {
757  std::list<ServiceBrowseHandler *>::iterator i;
758  for ( i = __handlers[type].begin(); i != __handlers[type].end(); ++i) {
759  (*i)->cache_exhausted();
760  }
761  }
762 }
763 
764 
765 /** Callback for Avahi.
766  * Callback called by Avahi.
767  * @param b service browser
768  * @param interface interface index
769  * @param protocol protocol
770  * @param event event
771  * @param name name
772  * @param type type
773  * @param domain domain
774  * @param flags flags
775  * @param instance pointer to the AvahiThread instance that initiated
776  * the search
777  */
778 void
779 AvahiThread::browse_callback( AvahiServiceBrowser *b,
780  AvahiIfIndex interface,
781  AvahiProtocol protocol,
782  AvahiBrowserEvent event,
783  const char *name,
784  const char *type,
785  const char *domain,
786  AvahiLookupResultFlags flags,
787  void *instance)
788 {
789  AvahiThread *at = static_cast<AvahiThread *>(instance);
790 
791  switch (event) {
792  case AVAHI_BROWSER_FAILURE:
793  //printf("(Browser) %s\n", avahi_strerror(avahi_client_errno(avahi_service_browser_get_client(b))));
794  return;
795 
796  case AVAHI_BROWSER_NEW:
797  //printf("(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
798  // We ignore the returned resolver object. In the callback
799  // function we free it. If the server is terminated before
800  // the callback function is called the server will free
801  // the resolver for us.
802  if (!(avahi_service_resolver_new(at->client, interface, protocol,
803  name, type, domain, protocol, (AvahiLookupFlags)0,
804  AvahiThread::resolve_callback, instance))) {
805  throw NullPointerException("Could not instantiate resolver");
806  }
807  break;
808 
809  case AVAHI_BROWSER_REMOVE:
810  // handler
811  //printf("(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
812  at->call_handler_service_removed(name, type, domain);
813  break;
814 
815  case AVAHI_BROWSER_ALL_FOR_NOW:
816  // handler
817  //printf("(Browser) ALL_FOR_NOW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
818  at->call_handler_all_for_now(type);
819  break;
820 
821  case AVAHI_BROWSER_CACHE_EXHAUSTED:
822  // handler
823  //printf("(Browser) CACHE_EXHAUSTED: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
824  at->call_handler_cache_exhausted(type);
825  break;
826 
827  }
828 }
829 
830 
831 /** Callback for Avahi.
832  * Callback called by Avahi.
833  * @param r service resolver
834  * @param interface interface index
835  * @param protocol protocol
836  * @param event event
837  * @param name name
838  * @param type type
839  * @param domain domain
840  * @param host_name host name
841  * @param address address
842  * @param port port
843  * @param txt TXT records
844  * @param flags flags
845  * @param instance pointer to the AvahiThread instance that initiated
846  * the search
847  */
848 void
849 AvahiThread::resolve_callback( AvahiServiceResolver *r,
850  AvahiIfIndex interface,
851  AVAHI_GCC_UNUSED AvahiProtocol protocol,
852  AvahiResolverEvent event,
853  const char *name,
854  const char *type,
855  const char *domain,
856  const char *host_name,
857  const AvahiAddress *address,
858  uint16_t port,
859  AvahiStringList *txt,
860  AvahiLookupResultFlags flags,
861  void *instance)
862 {
863  AvahiThread *at = static_cast<AvahiThread *>(instance);
864 
865  switch (event) {
866  case AVAHI_RESOLVER_FAILURE:
867  // handler failure
868  at->call_handler_failed(name, type, domain);
869  break;
870 
871  case AVAHI_RESOLVER_FOUND:
872  // handler add
873  {
874  std::list< std::string > txts;
875  AvahiStringList *l = txt;
876 
877  txts.clear();
878  while ( l ) {
879  txts.push_back((char *)avahi_string_list_get_text(l));
880  l = avahi_string_list_get_next( l );
881  }
882 
883  at->call_handler_service_added(name, type, domain, host_name, interface, address, port, txts, flags);
884  }
885  break;
886  }
887 
888  avahi_service_resolver_free(r);
889 }
890 
891 
892 /* **********************************************************************************
893  * Avahi resolver methods
894  * **********************************************************************************/
895 
896 
897 /** Order name resolution.
898  * This initiates resolution of a name. The method immediately returns and will not
899  * wait for the result.
900  * @param name name to resolve.
901  * @param handler handler to call for the result
902  */
903 void
905 {
906  AvahiResolverCallbackData *data = new AvahiResolverCallbackData(this, handler);
907 
908  if ( __pending_hostname_resolves.find(name) == __pending_hostname_resolves.end()) {
909  __pending_hostname_resolves[name] = data;
910  }
911 
912  wake_poller();
913 }
914 
915 
916 void
917 AvahiThread::start_hostname_resolver(const char *name, AvahiResolverCallbackData *data)
918 {
919  AvahiHostNameResolver *resolver;
920  if ( (resolver = avahi_host_name_resolver_new(client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
921  name, service_protocol,
922  AVAHI_LOOKUP_USE_MULTICAST,
923  AvahiThread::host_name_resolver_callback,
924  data) ) == NULL ) {
925  throw Exception("Cannot create Avahi name resolver");
926  } else {
927  __running_hostname_resolvers.push_back(resolver);
928  }
929 
930 }
931 
932 
933 void
934 AvahiThread::start_hostname_resolvers()
935 {
937  for (phrit = __pending_hostname_resolves.begin(); phrit != __pending_hostname_resolves.end(); ++phrit) {
938  start_hostname_resolver(phrit->first.c_str(), phrit->second);
939  }
940  __pending_hostname_resolves.clear();
941 }
942 
943 
944 void
945 AvahiThread::start_address_resolvers()
946 {
948 
949  for (parit = __pending_address_resolves.begin(); parit != __pending_address_resolves.end(); ++parit) {
950  start_address_resolver(parit->first, parit->second);
951  free(parit->first);
952  }
953  __pending_address_resolves.clear();
954 }
955 
956 
957 /** Order address resolution.
958  * This initiates resolution of an address. The method immediately returns and will not
959  * wait for the result.
960  * @param addr address to resolve
961  * @param addrlen length of addr in bytes
962  * @param handler handler to call for the result
963  */
964 void
965 AvahiThread::resolve_address(struct sockaddr *addr, socklen_t addrlen,
966  AvahiResolverHandler *handler)
967 {
968  struct ::sockaddr_storage *sstor =
969  (struct ::sockaddr_storage *)malloc(sizeof(struct ::sockaddr_storage));
970  if (addr->sa_family == AF_INET) {
971  if (addrlen != sizeof(sockaddr_in)) {
972  throw Exception("Invalid size for IPv4 address struct");
973  }
974  memcpy(sstor, addr, sizeof(sockaddr_in));
975  } else if (addr->sa_family == AF_INET6) {
976  if (addrlen != sizeof(sockaddr_in6)) {
977  throw Exception("Invalid size for IPv6 address struct");
978  }
979  memcpy(sstor, addr, sizeof(sockaddr_in6));
980  } else {
981  throw Exception("Unknown address family");
982  }
983  AvahiResolverCallbackData *data = new AvahiResolverCallbackData(this, handler);
984 
985  __pending_address_resolves[sstor] = data;
986  wake_poller();
987 }
988 
989 
990 void
991 AvahiThread::start_address_resolver(const struct sockaddr_storage *in_addr, AvahiResolverCallbackData *data)
992 {
993  AvahiAddress a;
994 
995  if (in_addr->ss_family == AF_INET) {
996  a.proto = AVAHI_PROTO_INET;
997  a.data.ipv4.address = ((sockaddr_in *)in_addr)->sin_addr.s_addr;
998  } else if (in_addr->ss_family == AF_INET6) {
999  a.proto = AVAHI_PROTO_INET6;
1000  memcpy(&a.data.ipv6.address, &((sockaddr_in6 *)in_addr)->sin6_addr, sizeof(in6_addr));
1001  } else {
1002  throw Exception("Unknown address family");
1003  }
1004 
1005  AvahiAddressResolver *resolver;
1006  if ( (resolver = avahi_address_resolver_new(client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,
1007  &a, AVAHI_LOOKUP_USE_MULTICAST,
1008  AvahiThread::address_resolver_callback,
1009  data) ) == NULL ) {
1010  Exception e("Cannot create Avahi address resolver");
1011  e.append("Avahi error: %s", avahi_strerror(avahi_client_errno(client)));
1012  throw e;
1013  } else {
1014  __running_address_resolvers.push_back_locked(resolver);
1015  }
1016 }
1017 
1018 
1019 /** Remove hostname resolver.
1020  * Used internally by callback.
1021  * @param r resolver
1022  */
1023 void
1024 AvahiThread::remove_hostname_resolver(AvahiHostNameResolver *r)
1025 {
1026  __running_hostname_resolvers.remove_locked(r);
1027 }
1028 
1029 
1030 /** Remove address resolver.
1031  * Used internally by callback.
1032  * @param r resolver
1033  */
1034 void
1035 AvahiThread::remove_address_resolver(AvahiAddressResolver *r)
1036 {
1037  __running_address_resolvers.remove_locked(r);
1038 }
1039 
1040 
1041 /** Internal callback.
1042  * Callback for avahi.
1043  */
1044 void
1045 AvahiThread::host_name_resolver_callback(AvahiHostNameResolver *r,
1046  AvahiIfIndex interface,
1047  AvahiProtocol protocol,
1048  AvahiResolverEvent event,
1049  const char *name,
1050  const AvahiAddress *a,
1051  AvahiLookupResultFlags flags,
1052  void *userdata)
1053 {
1054  AvahiResolverCallbackData *cd = static_cast<AvahiResolverCallbackData *>(userdata);
1055 
1056  cd->first->remove_hostname_resolver(r);
1057  avahi_host_name_resolver_free(r);
1058 
1059  switch (event) {
1060  case AVAHI_RESOLVER_FOUND:
1061  {
1062  if (protocol == AVAHI_PROTO_INET) {
1063  struct sockaddr_in *res = (struct sockaddr_in *)malloc(sizeof(struct sockaddr_in));
1064  res->sin_family = (unsigned short)avahi_proto_to_af(protocol);
1065  res->sin_addr.s_addr = a->data.ipv4.address;
1066  cd->second->resolved_name(strdup(name), (struct sockaddr *)res, sizeof(struct sockaddr_in));
1067  } else if (protocol == AVAHI_PROTO_INET6) {
1068  struct sockaddr_in6 *res = (struct sockaddr_in6 *)malloc(sizeof(struct sockaddr_in6));
1069  res->sin6_family = (unsigned short)avahi_proto_to_af(protocol);
1070  memcpy(&res->sin6_addr, &a->data.ipv6.address, sizeof(in6_addr));
1071  cd->second->resolved_name(strdup(name), (struct sockaddr *)res, sizeof(struct sockaddr_in6));
1072  } else { // don't know
1073  cd->second->name_resolution_failed(strdup(name));
1074  }
1075  }
1076  break;
1077 
1078  case AVAHI_RESOLVER_FAILURE:
1079  default:
1080  cd->second->name_resolution_failed(strdup(name));
1081  break;
1082  }
1083 
1084  delete cd;
1085 }
1086 
1087 
1088 /** Internal callback.
1089  * Callback for avahi.
1090  */
1091 void
1092 AvahiThread::address_resolver_callback(AvahiAddressResolver *r,
1093  AvahiIfIndex interface,
1094  AvahiProtocol protocol,
1095  AvahiResolverEvent event,
1096  const AvahiAddress *a,
1097  const char *name,
1098  AvahiLookupResultFlags flags,
1099  void *userdata)
1100 {
1101  AvahiResolverCallbackData *cd = static_cast<AvahiResolverCallbackData *>(userdata);
1102 
1103  cd->first->remove_address_resolver(r);
1104  avahi_address_resolver_free(r);
1105 
1106  struct sockaddr *res = NULL;
1107  socklen_t res_size = 0;
1108 
1109  if (protocol == AVAHI_PROTO_INET) {
1110  res_size = sizeof(struct sockaddr_in);
1111  res = (struct sockaddr *)malloc(res_size);
1112  sockaddr_in *res_4 = (struct sockaddr_in *)res;
1113  res_4->sin_family = (unsigned short)avahi_proto_to_af(protocol);
1114  res_4->sin_addr.s_addr = a->data.ipv4.address;
1115  } else if (protocol == AVAHI_PROTO_INET6) {
1116  res_size = sizeof(struct sockaddr_in6);
1117  res = (struct sockaddr *)malloc(res_size);
1118  sockaddr_in6 *res_6 = (struct sockaddr_in6 *)res;
1119  res_6->sin6_family = (unsigned short)avahi_proto_to_af(protocol);
1120  memcpy(&res_6->sin6_addr, &a->data.ipv6.address, sizeof(in6_addr));
1121  }
1122 
1123  switch (event) {
1124  case AVAHI_RESOLVER_FOUND:
1125  cd->second->resolved_address(res, res_size, strdup(name));
1126  break;
1127  case AVAHI_RESOLVER_FAILURE:
1128  cd->second->address_resolution_failed(res, res_size);
1129  break;
1130 
1131  default:
1132  cd->second->address_resolution_failed(NULL, 0);
1133  break;
1134  }
1135 
1136  delete cd;
1137 }
1138 
1139 
1140 /** Unlocks init lock.
1141  * Only to be called by client_callback().
1142  */
1143 void
1144 AvahiThread::init_done()
1145 {
1146  wake_poller();
1147  init_wc->wake_all();
1148 }
1149 
1150 
1151 /** Waits for the AvahiThread to be initialized.
1152  * You can use this if you want to wait until the thread has been
1153  * fully initialized and may be used. Since the happens in this thread
1154  * it is in general not immediately ready after start().
1155  * This will block the calling thread until the AvahiThread has
1156  * been initialized. This is done by waiting for a release of an
1157  * initialization mutex.
1158  */
1159 void
1161 {
1162  init_wc->wait();
1163 }
1164 
1165 } // 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