OverSim
ZeroconfConnector.cc
Go to the documentation of this file.
1 //
2 // Copyright (C) 2008 Institut fuer Telematik, Universitaet Karlsruhe (TH)
3 //
4 // This program is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU General Public License
6 // as published by the Free Software Foundation; either version 2
7 // of the License, or (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with this program; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
17 //
18 
24 #include "ZeroconfConnector.h"
25 
27 
28 #ifdef HAVE_AVAHI
29 
30 #include <stdio.h>
31 #include <assert.h>
32 #include <stdlib.h>
33 #include <time.h>
34 #include <semaphore.h>
35 #include <iostream>
36 #include <arpa/inet.h>
37 #include <sys/socket.h>
38 #include <netinet/in.h>
39 
40 #include <BaseOverlay.h>
41 #include <GlobalNodeListAccess.h>
42 #include <BootstrapList.h>
43 
44 using namespace std;
45 
46 // TODO cleanup code
47 void entry_group_callback(AvahiEntryGroup *g, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata) {
48  AvahiClient *client = avahi_entry_group_get_client(g);
49  assert(client);
50  ZeroconfConnector *zConfigurator = (ZeroconfConnector *)userdata;
51  assert(zConfigurator);
52 
53  /* Called whenever the entry group state changes */
54  switch (state) {
55  case AVAHI_ENTRY_GROUP_ESTABLISHED :
56  /* The entry group has been established successfully */
57  cerr << "Service " << zConfigurator->serviceName << " successfully established." << endl;
58  break;
59 
60  case AVAHI_ENTRY_GROUP_COLLISION : {
61  char *n;
62 
63  /* A service name collision happened. Pick a new name */
64  n = avahi_alternative_service_name(zConfigurator->serviceName);
65  avahi_free(zConfigurator->serviceName);
66  zConfigurator->serviceName = n;
67 
68  cerr << "Service name collision, renaming service to " << n << "." << endl;
69 
70  /* Recreate the services with new name*/
71  create_services(client, zConfigurator);
72  break;
73  }
74 
75  case AVAHI_ENTRY_GROUP_FAILURE :
76 
77  cerr << "Entry group failure: " << avahi_strerror(avahi_client_errno(client)) << endl;
78 
79  avahi_threaded_poll_quit(zConfigurator->threadedPoll);
80  break;
81 
82  default:
83  break;
84  }
85 }
86 
87 
88 void create_services(AvahiClient *c, ZeroconfConnector *zConfigurator) {
89  char *n, r[3][128];
90  int ret;
91 
92  assert(c);
93  assert(zConfigurator);
94 
95  if (!zConfigurator) {
96  cerr << "ZeroconfConnector not available, can not create service" << endl;
97  return;
98  }
99 
100  /*
101  * If this is the first time we're called, let's create a new
102  * entry group if necessary
103  */
104  if (!zConfigurator->group)
105  if (!(zConfigurator->group = avahi_entry_group_new(c, entry_group_callback, zConfigurator))) {
106  cerr << "avahi_entry_group_new() failed: " << avahi_strerror(avahi_client_errno(c)) << endl;
107  goto fail;
108  }
109 
110  /*
111  * If the group is empty (either because it was just created, or
112  * because it was reset previously, add our entries
113  */
114  if (avahi_entry_group_is_empty(zConfigurator->group)) {
115  cerr << "Adding service " << zConfigurator->serviceName << endl;
116 
117  snprintf(r[0], sizeof(r[0]), "peerid=%s", (zConfigurator->thisNode->key.toString(16)).c_str());
118  snprintf(r[1], sizeof(r[1]), "overlayid=%s", zConfigurator->overlayName);
119  snprintf(r[2], sizeof(r[2]), "overlaytype=%s", zConfigurator->overlayType);
120 
121 
122  if ((ret = avahi_entry_group_add_service(zConfigurator->group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC,(AvahiPublishFlags)0,
123  zConfigurator->serviceName, zConfigurator->serviceType, NULL, NULL, zConfigurator->thisNode->getPort(), r[0], r[1], r[2], NULL)) < 0) {
124 
125  if (ret == AVAHI_ERR_COLLISION)
126  goto collision;
127 
128  cerr << "Failed to add _p2pbootstrap._udp service: " << avahi_strerror(ret) << endl;
129  goto fail;
130  }
131 
132  /* Tell the server to register the service */
133  if ((ret = avahi_entry_group_commit(zConfigurator->group)) < 0) {
134  cerr << "Failed to commit entry group: " << avahi_strerror(ret) << endl;
135  goto fail;
136  }
137  }
138 
139  return;
140 
141 collision:
142 
143 
144  /* A service name collision happened. Pick a new name */
145  n = avahi_alternative_service_name(zConfigurator->serviceName);
146  avahi_free(zConfigurator->serviceName);
147  zConfigurator->serviceName = n;
148 
149  cerr << "Service name collision, renaming service to " << n << endl;
150 
151  avahi_entry_group_reset(zConfigurator->group);
152 
153  create_services(c, zConfigurator);
154  return;
155 
156 fail:
157  avahi_threaded_poll_quit(zConfigurator->threadedPoll);
158 }
159 
160 void resolv_callback(
161  AvahiServiceResolver *r,
162  AVAHI_GCC_UNUSED AvahiIfIndex interface,
163  AVAHI_GCC_UNUSED AvahiProtocol protocol,
164  AvahiResolverEvent event,
165  const char *name,
166  const char *type,
167  const char *domain,
168  const char *host_name,
169  const AvahiAddress *address,
170  uint16_t port,
171  AvahiStringList *txt,
172  AvahiLookupResultFlags flags,
173  AVAHI_GCC_UNUSED void* userdata) {
174 
175  char *peerID = NULL;
176  char *overlayType = NULL;
177  AvahiStringList *record;
178  AvahiClient *client = NULL;
179  ZeroconfConnector *zConfigurator = NULL;
180 
181  assert(r);
182 
183  client = avahi_service_resolver_get_client(r);
184  assert(client);
185 
186  zConfigurator = (ZeroconfConnector *)userdata;
187  assert(zConfigurator);
188 
189 
190  /* Called whenever a service has been resolved successfully or timed out */
191  switch (event) {
192  case AVAHI_RESOLVER_FAILURE:
193  cerr << "(Resolver) Failed to resolve service " << name << " of type " << type << " in domain " <<
194  domain << ": " << avahi_strerror(avahi_client_errno(client)) << endl;
195  break;
196 
197  case AVAHI_RESOLVER_FOUND: {
198  char a[AVAHI_ADDRESS_STR_MAX], *t;
199 
200  cout << "Service " << name << " of type " << type << " in domain " << domain << endl;
201 
202  avahi_address_snprint(a, sizeof(a), address);
203  t = avahi_string_list_to_string(txt);
204  fprintf(stderr,
205  "\t%s:%u (%s)\n"
206  "\tTXT=%s\n"
207  "\tcookie is %u\n"
208  "\tis_local: %i\n"
209  "\tour_own: %i\n"
210  "\twide_area: %i\n"
211  "\tmulticast: %i\n"
212  "\tcached: %i\n",
213  host_name, port, a,
214  t,
215  avahi_string_list_get_service_cookie(txt),
216  !!(flags & AVAHI_LOOKUP_RESULT_LOCAL),
217  !!(flags & AVAHI_LOOKUP_RESULT_OUR_OWN),
218  !!(flags & AVAHI_LOOKUP_RESULT_WIDE_AREA),
219  !!(flags & AVAHI_LOOKUP_RESULT_MULTICAST),
220  !!(flags & AVAHI_LOOKUP_RESULT_CACHED));
221 
222  avahi_free(t);
223 
224  /* parse the TXT record to search for the overlay algorithm and nodeID of the node */
225  for (record = txt; record; record = record->next) {
226  if (!strncmp((char *)record->text, "overlaytype", 11)) {
227  if ((char)record->text[11] == '=') {
228  overlayType = avahi_strdup((const char *)(record->text + 12));
229  }
230  } else {
231  if (!strncmp((char *)record->text, "peerid", 6)) {
232  if (record->text[6] == '=') {
233  peerID = avahi_strdup((const char *)(record->text + 7));
234  }
235  }
236  }
237  }
238 
239  if (!overlayType || !peerID || strncmp(overlayType, zConfigurator->overlayType,
240  strlen(overlayType))) {
241  cerr << "TXT record of the node is defect, or node does not use the desired p2p algorithm." << endl;
242 
243  if (overlayType)
244  avahi_free(overlayType);
245 
246  if (peerID)
247  avahi_free(peerID);
248 
249  break;
250  }
251 
252  if (zConfigurator) {
253  BootstrapNodeHandle *node = new BootstrapNodeHandle(OverlayKey(peerID, 16),
254  IPvXAddress(a), (int)port,
255  !!(flags & AVAHI_LOOKUP_RESULT_WIDE_AREA) ? DNSSD : MDNS);
256 
257  if (node) {
258  zConfigurator->insertNode(avahi_strdup(name), node);
259  } else {
260  cerr << "Failed to allocate memory for NodeHandle" << endl;
261  }
262  }
263 
264  avahi_free(overlayType);
265  avahi_free(peerID);
266 
267  break;
268  }
269 
270  default:
271  break;
272  }
273 
274  avahi_service_resolver_free(r);
275 
276  return;
277 }
278 
279 void browse_callback(
280  AvahiServiceBrowser *b,
281  AvahiIfIndex interface,
282  AvahiProtocol protocol,
283  AvahiBrowserEvent event,
284  const char *name,
285  const char *type,
286  const char *domain,
287  AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
288  void* userdata) {
289 
290  AvahiClient *c = NULL;
291  ZeroconfConnector *zConfigurator = NULL;
292  assert(b);
293 
294  c = avahi_service_browser_get_client(b);
295  assert(c);
296 
297  zConfigurator = (ZeroconfConnector *)userdata;
298  assert(zConfigurator);
299 
300  /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
301  switch (event) {
302  case AVAHI_BROWSER_FAILURE:
303  cerr << "(Browser) " << avahi_strerror(avahi_client_errno(c)) << endl;
304  avahi_threaded_poll_quit(zConfigurator->threadedPoll);
305  return;
306 
307  case AVAHI_BROWSER_NEW:
308  cerr << "(Browser) NEW: service " << name << " of type " << type << " in domain " << domain << endl;
309 
310  if (strcmp(name, zConfigurator->serviceName)) {
311  if (!(avahi_service_resolver_new(c, interface, protocol, name, type, domain, AVAHI_PROTO_INET, (AvahiLookupFlags)0, resolv_callback, zConfigurator)))
312  cerr << "Failed to resolve service " << name << ":" << avahi_strerror(avahi_client_errno(c)) << endl;
313  }
314  break;
315 
316  case AVAHI_BROWSER_REMOVE:
317  cerr << "(Browser) REMOVE: service " << name << " of type " << type << " in domain " << domain << endl;
318  zConfigurator->removeNode((char *)name);
319  break;
320 
321  case AVAHI_BROWSER_ALL_FOR_NOW:
322  case AVAHI_BROWSER_CACHE_EXHAUSTED:
323  cerr << "(Browser) " << (event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW") << endl;
324  break;
325 
326  default:
327  break;
328  }
329 }
330 
331 
332 void client_callback(
333  AvahiClient *c,
334  AvahiClientState state,
335  AVAHI_GCC_UNUSED void * userdata) {
336  ZeroconfConnector *zConfigurator = NULL;
337 
338  assert(c);
339  zConfigurator = (ZeroconfConnector *)userdata;
340  assert(zConfigurator);
341 
342  /* Called whenever the client or server state changes */
343  switch (state) {
344  case AVAHI_CLIENT_FAILURE:
345  cerr << "Client failure: " << avahi_strerror(avahi_client_errno(c)) << endl;
346  avahi_threaded_poll_quit(zConfigurator->threadedPoll);
347 
348  break;
349 
350  case AVAHI_CLIENT_S_REGISTERING:
351  if (zConfigurator->group)
352  avahi_entry_group_reset(zConfigurator->group);
353 
354  break;
355 
356  default:
357  break;
358  }
359 }
360 
361 
363 {
364  initResult = AVAHI_INIT_FAILED;
365 
366  client = NULL;
367  group = NULL;
368  threadedPoll = NULL;
369 
370  sbMDNS = sbUDNS = NULL;
371 
372  serviceType = overlayName = overlayType = NULL;
373  serviceName = NULL;
374  thisNode = NULL;
375 
376  if (sem_init(&nodeSetSem, 0, 1) == -1) {
377  cerr << "nodeSetSem can't be initialized." << endl;
378  }
379 
380  pollingTimer = NULL;
381 
382  return;
383 }
384 
385 
387 {
388  if (!enabled) {
389  return;
390  }
391 
392  LocalBNodeSet::iterator iter;
393  cancelAndDelete(pollingTimer);
394 
395  avahi_threaded_poll_stop(threadedPoll);
396  avahi_service_browser_free(sbMDNS);
397  avahi_service_browser_free(sbUDNS);
398 
399  if (group)
400  avahi_entry_group_free(group);
401 
402  avahi_client_free(client);
403  avahi_threaded_poll_free(threadedPoll);
404 
405  sem_destroy(&nodeSetSem);
406 
407  for (iter = newSet.begin(); iter != newSet.end(); iter++) {
408  avahi_free(iter->first);
409  delete iter->second;
410  }
411  newSet.clear();
412 
413  if (serviceName)
414  avahi_free(serviceName);
415 
416  if (thisNode)
417  delete thisNode;
418 
419  return;
420 }
421 
422 
424 {
425  if (node.isUnspecified()) {
426  cerr << "unspecified node for service announcement" << endl;
427  return;
428  }
429 
430  if (!(thisNode = new NodeHandle(node))) {
431  cerr << "no resource to save local node" << endl;
432  return;
433  }
434 
435  create_services(client, this);
436 
437  return;
438 }
439 
441 {
442  /*
443  * Reset the avahi entry group to revoke
444  * bootstrap service.
445  */
446  if (group)
447  avahi_entry_group_reset(group);
448 
449  return;
450 }
451 
452 
453 int ZeroconfConnector::insertNode(char *name, BootstrapNodeHandle *node)
454 {
455  if ((!node)) {
456  cerr << "Trying to insert invalid node" << endl;
457  return -1;
458  }
459 
460  cerr << "insertNode called" << endl;
461 
462  sem_wait(&nodeSetSem);
463  newSet.insert(LocalNodePair(name, node));
464 
465  sem_post(&nodeSetSem);
466 
467  return 0;
468 }
469 
470 
471 int ZeroconfConnector::removeNode(char *name)
472 {
473  LocalBNodeSet::iterator iter;
474  if (!name) {
475  cerr << "node name invalid" << endl;
476  return -1;
477  }
478 
479  sem_wait(&nodeSetSem);
480 
481  iter = newSet.find((char *)name);
482  if (iter != newSet.end()) {
483  delete iter->second;
484  avahi_free(iter->first);
485  newSet.erase(iter);
486  }
487 
488  sem_post(&nodeSetSem);
489  return 0;
490 }
491 
492 
493 inline int ZeroconfConnector::getInitResult()
494 {
495  return initResult;
496 }
497 
498 
499 void ZeroconfConnector::initialize()
500 {
501  int error;
502 
503  enabled = par("enableZeroconf");
504  serviceType = par("serviceType");
505  serviceName = avahi_strdup(par("serviceName"));
506  overlayType = par("overlayType");
507  overlayName = par("overlayName");
508 
509  if (!enabled) {
510  return;
511  }
512 
513  if (!(threadedPoll = avahi_threaded_poll_new())) {
514  cerr << "Failed to create a threaded poll." << endl;
515  goto fail;
516  }
517 
518 
519  if (!(client = avahi_client_new(avahi_threaded_poll_get(threadedPoll), (AvahiClientFlags)0, client_callback,
520  this, &error))) {
521  cerr << "Failed to create a client." << endl;
522  goto fail;
523  }
524 
525  /* using AVAHI_PROTO_INET means we only need IPv4 informaion */
526  if (!(sbMDNS = avahi_service_browser_new(client, AVAHI_IF_UNSPEC, AVAHI_PROTO_INET, serviceType, NULL,
527  (AvahiLookupFlags)0, browse_callback, this))) {
528  cerr << "Failed to create a service browser." << endl;
529  goto fail;
530  }
531 
532  if (!(sbUDNS = avahi_service_browser_new(client, AVAHI_IF_UNSPEC, AVAHI_PROTO_INET, serviceType, overlayName,
533  (AvahiLookupFlags)0, browse_callback, this))) {
534  cerr << "Failed to create a service browser." << endl;
535  goto fail;
536  }
537 
538  if (avahi_threaded_poll_start(threadedPoll) < 0) {
539  cerr << "Failed to start the threaded poll." << endl;
540  goto fail;
541  }
542 
543  pollingTimer = new cMessage("Zeroconf timer");
544 
545  scheduleAt(simTime() + 1, pollingTimer);
546  initResult = AVAHI_INIT_SUCCEEDED;
547 
548  return;
549 
550 fail:
551  if (sbMDNS)
552  avahi_service_browser_free(sbMDNS);
553 
554  if (sbUDNS)
555  avahi_service_browser_free(sbUDNS);
556 
557  if (client)
558  avahi_client_free(client);
559 
560  if (threadedPoll)
561  avahi_threaded_poll_free(threadedPoll);
562 
563  if (pollingTimer)
564  delete pollingTimer;
565 
566  return;
567 }
568 
569 void ZeroconfConnector::handleMessage(cMessage *msg)
570 {
571  char *name;
572  BootstrapNodeHandle *node;
573  LocalBNodeSet::iterator tempIter;
574 
575  BootstrapList *bootstrapList =
576  check_and_cast<BootstrapList *>(getParentModule()->getSubmodule("singleHost", 0)->getSubmodule("bootstrapList", 0));
577 
578  if (msg->isSelfMessage()) {
579  if (!sem_trywait(&nodeSetSem)) {
580  if (!newSet.empty()) {
581  for (LocalBNodeSet::iterator iter = newSet.begin(); iter != newSet.end();) {
582  name = iter->first;
583  node = iter->second;
584  tempIter = iter;
585  iter++;
586 
587  /*
588  * Insert bootstrap node into bootstrapList. After
589  * insertion, BootstrapList is responsible for deleting
590  * these nodes
591  */
592  bootstrapList->insertBootstrapCandidate(*node);
593  newSet.erase(tempIter);
594  avahi_free(name);
595  }
596 
597  newSet.clear();
598  }
599 
600  sem_post(&nodeSetSem);
601  }
602  }
603 
604  scheduleAt(simTime() + 5, msg);
605 
606  return;
607 }
608 
609 #endif