2 #include "meshlink_internal.h"
9 #include <avahi-core/core.h>
10 #include <avahi-core/lookup.h>
11 #include <avahi-core/publish.h>
12 #include <avahi-core/log.h>
13 #include <avahi-common/simple-watch.h>
14 #include <avahi-common/malloc.h>
15 #include <avahi-common/alternative.h>
16 #include <avahi-common/error.h>
18 #include <netinet/in.h>
20 #include <uuid/uuid.h>
22 #define MESHLINK_MDNS_SERVICE_TYPE "_%s._tcp"
23 #define MESHLINK_MDNS_NAME_KEY "name"
24 #define MESHLINK_MDNS_FINGERPRINT_KEY "fingerprint"
26 static void discovery_entry_group_callback(AvahiServer *server, AvahiSEntryGroup *group, AvahiEntryGroupState state, void *userdata)
28 meshlink_handle_t *mesh = userdata;
32 assert(mesh->avahi_server != NULL);
33 assert(mesh->avahi_poll != NULL);
35 /* Called whenever the entry group state changes */
38 case AVAHI_ENTRY_GROUP_ESTABLISHED:
39 /* The entry group has been established successfully */
40 logger(mesh, MESHLINK_DEBUG, "Avahi Service successfully established.\n");
43 case AVAHI_ENTRY_GROUP_COLLISION:
44 logger(mesh, MESHLINK_WARNING, "Avahi Service collision.\n");
45 // @TODO can we just set a new name and retry?
48 case AVAHI_ENTRY_GROUP_FAILURE :
49 /* Some kind of failure happened while we were registering our services */
50 logger(mesh, MESHLINK_ERROR, "Avahi Entry group failure: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
51 avahi_simple_poll_quit(mesh->avahi_poll);
54 case AVAHI_ENTRY_GROUP_UNCOMMITED:
55 case AVAHI_ENTRY_GROUP_REGISTERING:
61 static void discovery_create_services(meshlink_handle_t *mesh)
63 char *txt_name = NULL;
67 assert(mesh->name != NULL);
68 assert(mesh->myport != NULL);
69 assert(mesh->avahi_server != NULL);
70 assert(mesh->avahi_poll != NULL);
71 assert(mesh->avahi_servicetype != NULL);
72 assert(mesh->self != NULL);
74 logger(mesh, MESHLINK_DEBUG, "Adding service\n");
76 /* Ifthis is the first time we're called, let's create a new entry group */
77 if(!mesh->avahi_group)
79 if(!(mesh->avahi_group = avahi_s_entry_group_new(mesh->avahi_server, discovery_entry_group_callback, mesh)))
81 logger(mesh, MESHLINK_ERROR, "avahi_entry_group_new() failed: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
86 /* Create txt records */
87 size_t txt_name_len = sizeof(MESHLINK_MDNS_NAME_KEY) + 1 + strlen(mesh->name) + 1;
88 txt_name = malloc(txt_name_len);
92 logger(mesh, MESHLINK_ERROR, "Could not allocate memory for TXT record\n");
96 snprintf(txt_name, txt_name_len, "%s=%s", MESHLINK_MDNS_NAME_KEY, mesh->name);
98 char txt_fingerprint[sizeof(MESHLINK_MDNS_FINGERPRINT_KEY) + 1 + MESHLINK_FINGERPRINTLEN + 1];
99 snprintf(txt_fingerprint, sizeof(txt_fingerprint), "%s=%s", MESHLINK_MDNS_FINGERPRINT_KEY, meshlink_get_fingerprint(mesh, (meshlink_node_t *)mesh->self));
101 /* Add the service */
103 if((ret = avahi_server_add_service(mesh->avahi_server, mesh->avahi_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, meshlink_get_fingerprint(mesh, (meshlink_node_t *)mesh->self), mesh->avahi_servicetype, NULL, NULL, atoi(mesh->myport), txt_name, txt_fingerprint, NULL)) < 0)
105 logger(mesh, MESHLINK_ERROR, "Failed to add service: %s\n", avahi_strerror(ret));
109 /* Tell the server to register the service */
110 if((ret = avahi_s_entry_group_commit(mesh->avahi_group)) < 0)
112 logger(mesh, MESHLINK_ERROR, "Failed to commit entry_group: %s\n", avahi_strerror(ret));
119 avahi_simple_poll_quit(mesh->avahi_poll);
126 static void discovery_server_callback(AvahiServer *server, AvahiServerState state, void * userdata)
128 meshlink_handle_t *mesh = userdata;
131 assert(mesh != NULL);
135 case AVAHI_SERVER_RUNNING:
137 /* The serve has startup successfully and registered its host
138 * name on the network, so it's time to create our services */
139 if(!mesh->avahi_group)
141 discovery_create_services(mesh);
146 case AVAHI_SERVER_COLLISION:
149 assert(mesh->avahi_server != NULL);
150 assert(mesh->avahi_poll != NULL);
152 /* A host name collision happened. Let's pick a new name for the server */
154 uuid_generate(hostname);
156 char hostnamestr[36+1];
157 uuid_unparse_lower(hostname, hostnamestr);
159 logger(mesh, MESHLINK_WARNING, "Avahi host name collision, retrying with '%s'\n", hostnamestr);
160 int result = avahi_server_set_host_name(mesh->avahi_server, hostnamestr);
164 logger(mesh, MESHLINK_ERROR, "Avahi failed to set new host name: %s\n", avahi_strerror(result));
165 avahi_simple_poll_quit(mesh->avahi_poll);
170 case AVAHI_SERVER_REGISTERING:
172 /* Let's drop our registered services. When the server is back
173 * in AVAHI_SERVER_RUNNING state we will register them
174 * again with the new host name. */
175 if(mesh->avahi_group)
177 avahi_s_entry_group_reset(mesh->avahi_group);
178 mesh->avahi_group = NULL;
183 case AVAHI_SERVER_FAILURE:
186 assert(mesh->avahi_server != NULL);
187 assert(mesh->avahi_poll != NULL);
189 /* Terminate on failure */
190 logger(mesh, MESHLINK_ERROR, "Avahi server failure: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
191 avahi_simple_poll_quit(mesh->avahi_poll);
195 case AVAHI_SERVER_INVALID:
200 static void discovery_resolve_callback(AvahiSServiceResolver *resolver, AvahiIfIndex interface, AvahiProtocol protocol, AvahiResolverEvent event, const char *name, const char *type, const char *domain, const char *host_name, const AvahiAddress *address, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, void* userdata)
202 meshlink_handle_t *mesh = userdata;
205 assert(resolver != NULL);
206 assert(mesh != NULL);
207 assert(mesh->avahi_server != NULL);
209 /* Called whenever a service has been resolved successfully or timed out */
212 case AVAHI_RESOLVER_FAILURE:
215 assert(name != NULL);
216 assert(type != NULL);
217 assert(domain != NULL);
219 logger(mesh, MESHLINK_WARNING, "(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s\n", name, type, domain, avahi_strerror(avahi_server_errno(mesh->avahi_server)));
223 case AVAHI_RESOLVER_FOUND:
226 assert(name != NULL);
227 assert(type != NULL);
228 assert(domain != NULL);
229 assert(host_name != NULL);
230 assert(address != NULL);
233 char straddr[AVAHI_ADDRESS_STR_MAX], *strtxt;
235 logger(mesh, MESHLINK_DEBUG, "(Resolver) Service '%s' of type '%s' in domain '%s':\n", name, type, domain);
237 avahi_address_snprint(straddr, sizeof(straddr), address);
238 strtxt = avahi_string_list_to_string(txt);
239 logger(mesh, MESHLINK_DEBUG,
247 host_name, port, straddr,
249 avahi_string_list_get_service_cookie(txt),
250 !!(flags & AVAHI_LOOKUP_RESULT_LOCAL),
251 !!(flags & AVAHI_LOOKUP_RESULT_WIDE_AREA),
252 !!(flags & AVAHI_LOOKUP_RESULT_MULTICAST),
253 !!(flags & AVAHI_LOOKUP_RESULT_CACHED));
256 // retrieve fingerprint
257 AvahiStringList *node_name_li = avahi_string_list_find(txt, MESHLINK_MDNS_NAME_KEY);
258 AvahiStringList *node_fp_li = avahi_string_list_find(txt, MESHLINK_MDNS_FINGERPRINT_KEY);
260 if(node_name_li != NULL && node_fp_li != NULL)
262 char *node_name = (char*)avahi_string_list_get_text(node_name_li) + strlen(MESHLINK_MDNS_NAME_KEY);
263 char *node_fp = (char*)avahi_string_list_get_text(node_fp_li) + strlen(MESHLINK_MDNS_FINGERPRINT_KEY);
265 if(node_name[0] == '=' && node_fp[0] == '=')
270 meshlink_node_t *node = meshlink_get_node(mesh, node_name);
274 logger(mesh, MESHLINK_INFO, "Node %s is part of the mesh network.\n", node->name);
277 memset(&naddress, 0, sizeof(naddress));
279 switch(address->proto)
281 case AVAHI_PROTO_INET:
283 naddress.in.sin_family = AF_INET;
284 naddress.in.sin_port = htons(port);
285 naddress.in.sin_addr.s_addr = address->data.ipv4.address;
289 case AVAHI_PROTO_INET6:
291 naddress.in6.sin6_family = AF_INET6;
292 naddress.in6.sin6_port = htons(port);
293 memcpy(naddress.in6.sin6_addr.s6_addr, address->data.ipv6.address, sizeof(naddress.in6.sin6_addr.s6_addr));
298 naddress.unknown.family = AF_UNKNOWN;
302 if(naddress.unknown.family != AF_UNKNOWN)
304 meshlink_hint_address(mesh, (meshlink_node_t *)node, (struct sockaddr*)&naddress);
308 logger(mesh, MESHLINK_WARNING, "Could not resolve node %s to a known address family type.\n", node->name);
313 logger(mesh, MESHLINK_WARNING, "Node %s is not part of the mesh network.\n", node_name);
318 logger(mesh, MESHLINK_WARNING, "TXT records invalid.\n");
323 logger(mesh, MESHLINK_WARNING, "TXT records missing.\n");
329 avahi_s_service_resolver_free(resolver);
332 static void discovery_browse_callback(AvahiSServiceBrowser *browser, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, const char *name, const char *type, const char *domain, AvahiLookupResultFlags flags, void* userdata)
334 meshlink_handle_t *mesh = userdata;
337 assert(mesh != NULL);
338 assert(mesh->avahi_server != NULL);
339 assert(mesh->avahi_poll != NULL);
341 /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
344 case AVAHI_BROWSER_FAILURE:
346 logger(mesh, MESHLINK_ERROR, "(Browser) %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
347 avahi_simple_poll_quit(mesh->avahi_poll);
351 case AVAHI_BROWSER_NEW:
354 assert(name != NULL);
355 assert(type != NULL);
356 assert(domain != NULL);
358 logger(mesh, MESHLINK_DEBUG, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
359 /* We ignore the returned resolver object. In the callback
360 function we free it. Ifthe server is terminated before
361 the callback function is called the server will free
362 the resolver for us. */
363 if(!(avahi_s_service_resolver_new(mesh->avahi_server, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, discovery_resolve_callback, mesh)))
365 logger(mesh, MESHLINK_DEBUG, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_server_errno(mesh->avahi_server)));
370 case AVAHI_BROWSER_REMOVE:
373 assert(name != NULL);
374 assert(type != NULL);
375 assert(domain != NULL);
377 logger(mesh, MESHLINK_DEBUG, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
381 case AVAHI_BROWSER_ALL_FOR_NOW:
382 case AVAHI_BROWSER_CACHE_EXHAUSTED:
384 logger(mesh, MESHLINK_DEBUG, "(Browser) %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
390 static void *discovery_loop(void *userdata)
392 meshlink_handle_t *mesh = userdata;
395 assert(mesh != NULL);
396 assert(mesh->avahi_poll != NULL);
398 avahi_simple_poll_loop(mesh->avahi_poll);
403 static void discovery_log_cb(AvahiLogLevel level, const char *txt)
405 meshlink_log_level_t mlevel = MESHLINK_CRITICAL;
409 case AVAHI_LOG_ERROR:
410 mlevel = MESHLINK_ERROR;
414 mlevel = MESHLINK_WARNING;
417 case AVAHI_LOG_NOTICE:
419 mlevel = MESHLINK_INFO;
422 case AVAHI_LOG_DEBUG:
423 mlevel = MESHLINK_DEBUG;
427 logger(NULL, mlevel, "%s\n", txt);
430 bool discovery_start(meshlink_handle_t *mesh)
432 logger(mesh, MESHLINK_DEBUG, "discovery_start called\n");
435 assert(mesh != NULL);
436 assert(mesh->avahi_poll == NULL);
437 assert(mesh->avahi_server == NULL);
438 assert(mesh->avahi_browser == NULL);
439 assert(mesh->discovery_threadstarted == false);
440 assert(mesh->avahi_servicetype == NULL);
443 avahi_set_log_function(discovery_log_cb);
445 // create service type string
446 size_t servicetype_strlen = sizeof(MESHLINK_MDNS_SERVICE_TYPE) + strlen(mesh->appname) + 1;
447 mesh->avahi_servicetype = malloc(servicetype_strlen);
449 if(mesh->avahi_servicetype == NULL)
451 logger(mesh, MESHLINK_ERROR, "Failed to allocate memory for service type string.\n");
455 snprintf(mesh->avahi_servicetype, servicetype_strlen, MESHLINK_MDNS_SERVICE_TYPE, mesh->appname);
457 // Allocate discovery loop object
458 if(!(mesh->avahi_poll = avahi_simple_poll_new()))
460 logger(mesh, MESHLINK_ERROR, "Failed to create discovery poll object.\n");
464 // generate some unique host name (we actually do not care about it)
466 uuid_generate(hostname);
468 char hostnamestr[36+1];
469 uuid_unparse_lower(hostname, hostnamestr);
471 // Let's set the host name for this server.
472 AvahiServerConfig config;
473 avahi_server_config_init(&config);
474 config.host_name = avahi_strdup(hostnamestr);
475 config.publish_workstation = 0;
476 config.disallow_other_stacks = 0;
477 config.publish_hinfo = 0;
478 config.publish_addresses = 1;
479 config.publish_no_reverse = 1;
481 /* Allocate a new server */
483 mesh->avahi_server = avahi_server_new(avahi_simple_poll_get(mesh->avahi_poll), &config, discovery_server_callback, mesh, &error);
485 /* Free the configuration data */
486 avahi_server_config_free(&config);
488 /* Check wether creating the server object succeeded */
489 if(!mesh->avahi_server)
491 logger(mesh, MESHLINK_ERROR, "Failed to create discovery server: %s\n", avahi_strerror(error));
495 // Create the service browser
496 if(!(mesh->avahi_browser = avahi_s_service_browser_new(mesh->avahi_server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, mesh->avahi_servicetype, NULL, 0, discovery_browse_callback, mesh)))
498 logger(mesh, MESHLINK_ERROR, "Failed to create discovery service browser: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
502 // Start the discovery thread
503 if(pthread_create(&mesh->discovery_thread, NULL, discovery_loop, mesh) != 0)
505 logger(mesh, MESHLINK_ERROR, "Could not start discovery thread: %s\n", strerror(errno));
506 memset(&mesh->discovery_thread, 0, sizeof mesh->discovery_thread);
510 mesh->discovery_threadstarted = true;
515 if(mesh->avahi_browser != NULL)
517 avahi_s_service_browser_free(mesh->avahi_browser);
518 mesh->avahi_browser = NULL;
521 if(mesh->avahi_server != NULL)
523 avahi_server_free(mesh->avahi_server);
524 mesh->avahi_server = NULL;
527 if(mesh->avahi_poll != NULL)
529 avahi_simple_poll_free(mesh->avahi_poll);
530 mesh->avahi_poll = NULL;
533 if(mesh->avahi_servicetype != NULL)
535 free(mesh->avahi_servicetype);
536 mesh->avahi_servicetype = NULL;
542 void discovery_stop(meshlink_handle_t *mesh)
544 logger(mesh, MESHLINK_DEBUG, "discovery_stop called\n");
547 assert(mesh != NULL);
552 avahi_simple_poll_quit(mesh->avahi_poll);
555 // Wait for the discovery thread to finish
556 if(mesh->discovery_threadstarted == true)
558 pthread_join(mesh->discovery_thread, NULL);
559 mesh->discovery_threadstarted = false;
562 // Clean up resources
563 if(mesh->avahi_browser != NULL)
565 avahi_s_service_browser_free(mesh->avahi_browser);
566 mesh->avahi_browser = NULL;
569 if(mesh->avahi_group)
571 avahi_s_entry_group_reset(mesh->avahi_group);
572 avahi_s_entry_group_free(mesh->avahi_group);
573 mesh->avahi_group = NULL;
576 if(mesh->avahi_server != NULL)
578 avahi_server_free(mesh->avahi_server);
579 mesh->avahi_server = NULL;
582 if(mesh->avahi_poll != NULL)
584 avahi_simple_poll_free(mesh->avahi_poll);
585 mesh->avahi_poll = NULL;
588 if(mesh->avahi_servicetype != NULL)
590 free(mesh->avahi_servicetype);
591 mesh->avahi_servicetype = NULL;