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-common/simple-watch.h>
13 #include <avahi-common/malloc.h>
14 #include <avahi-common/alternative.h>
15 #include <avahi-common/error.h>
17 #include <netinet/in.h>
19 #include <uuid/uuid.h>
21 #define MESHLINK_MDNS_SERVICE_TYPE "_%s._tcp"
22 #define MESHLINK_MDNS_NAME_KEY "name"
23 #define MESHLINK_MDNS_FINGERPRINT_KEY "fingerprint"
25 static void discovery_entry_group_callback(AvahiServer *server, AvahiSEntryGroup *group, AvahiEntryGroupState state, void *userdata)
27 meshlink_handle_t *mesh = userdata;
31 assert(mesh->avahi_server != NULL);
32 assert(mesh->avahi_poll != NULL);
34 /* Called whenever the entry group state changes */
37 case AVAHI_ENTRY_GROUP_ESTABLISHED:
38 /* The entry group has been established successfully */
39 logger(mesh, MESHLINK_DEBUG, "Avahi Service successfully established.\n");
42 case AVAHI_ENTRY_GROUP_COLLISION:
43 logger(mesh, MESHLINK_WARNING, "Avahi Service collision.\n");
44 // @TODO can we just set a new name and retry?
47 case AVAHI_ENTRY_GROUP_FAILURE :
48 /* Some kind of failure happened while we were registering our services */
49 logger(mesh, MESHLINK_ERROR, "Avahi Entry group failure: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
50 avahi_simple_poll_quit(mesh->avahi_poll);
53 case AVAHI_ENTRY_GROUP_UNCOMMITED:
54 case AVAHI_ENTRY_GROUP_REGISTERING:
60 static void discovery_create_services(meshlink_handle_t *mesh)
62 char *txt_name = NULL;
66 assert(mesh->name != NULL);
67 assert(mesh->myport != NULL);
68 assert(mesh->avahi_server != NULL);
69 assert(mesh->avahi_poll != NULL);
70 assert(mesh->avahi_servicetype != NULL);
71 assert(mesh->self != NULL);
73 logger(mesh, MESHLINK_DEBUG, "Adding service\n");
75 /* Ifthis is the first time we're called, let's create a new entry group */
76 if(!mesh->avahi_group)
78 if(!(mesh->avahi_group = avahi_s_entry_group_new(mesh->avahi_server, discovery_entry_group_callback, mesh)))
80 logger(mesh, MESHLINK_ERROR, "avahi_entry_group_new() failed: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
85 /* Create txt records */
86 size_t txt_name_len = sizeof(MESHLINK_MDNS_NAME_KEY) + 1 + strlen(mesh->name) + 1;
87 txt_name = malloc(txt_name_len);
91 logger(mesh, MESHLINK_ERROR, "Could not allocate memory for TXT record\n");
95 snprintf(txt_name, txt_name_len, "%s=%s", MESHLINK_MDNS_NAME_KEY, mesh->name);
97 char txt_fingerprint[sizeof(MESHLINK_MDNS_FINGERPRINT_KEY) + 1 + MESHLINK_FINGERPRINTLEN + 1];
98 snprintf(txt_fingerprint, sizeof(txt_fingerprint), "%s=%s", MESHLINK_MDNS_FINGERPRINT_KEY, meshlink_get_fingerprint(mesh, (meshlink_node_t *)mesh->self));
100 /* Add the service */
102 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)
104 logger(mesh, MESHLINK_ERROR, "Failed to add service: %s\n", avahi_strerror(ret));
108 /* Tell the server to register the service */
109 if((ret = avahi_s_entry_group_commit(mesh->avahi_group)) < 0)
111 logger(mesh, MESHLINK_ERROR, "Failed to commit entry_group: %s\n", avahi_strerror(ret));
118 avahi_simple_poll_quit(mesh->avahi_poll);
125 static void discovery_server_callback(AvahiServer *server, AvahiServerState state, void * userdata)
127 meshlink_handle_t *mesh = userdata;
130 assert(mesh != NULL);
134 case AVAHI_SERVER_RUNNING:
136 /* The serve has startup successfully and registered its host
137 * name on the network, so it's time to create our services */
138 if(!mesh->avahi_group)
140 discovery_create_services(mesh);
145 case AVAHI_SERVER_COLLISION:
148 assert(mesh->avahi_server != NULL);
149 assert(mesh->avahi_poll != NULL);
151 /* A host name collision happened. Let's pick a new name for the server */
153 uuid_generate(hostname);
155 char hostnamestr[36+1];
156 uuid_unparse_lower(hostname, hostnamestr);
158 logger(mesh, MESHLINK_WARNING, "Avahi host name collision, retrying with '%s'\n", hostnamestr);
159 int result = avahi_server_set_host_name(mesh->avahi_server, hostnamestr);
163 logger(mesh, MESHLINK_ERROR, "Avahi failed to set new host name: %s\n", avahi_strerror(result));
164 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 = 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 = 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 bool discovery_start(meshlink_handle_t *mesh)
405 logger(mesh, MESHLINK_DEBUG, "discovery_start called\n");
408 assert(mesh != NULL);
409 assert(mesh->avahi_poll == NULL);
410 assert(mesh->avahi_server == NULL);
411 assert(mesh->avahi_browser == NULL);
412 assert(mesh->discovery_threadstarted == false);
413 assert(mesh->avahi_servicetype == NULL);
415 // create service type string
416 size_t servicetype_strlen = sizeof(MESHLINK_MDNS_SERVICE_TYPE) + strlen(mesh->appname) + 1;
417 mesh->avahi_servicetype = malloc(servicetype_strlen);
419 if(mesh->avahi_servicetype == NULL)
421 logger(mesh, MESHLINK_ERROR, "Failed to allocate memory for service type string.\n");
425 snprintf(mesh->avahi_servicetype, servicetype_strlen, MESHLINK_MDNS_SERVICE_TYPE, mesh->appname);
427 // Allocate discovery loop object
428 if(!(mesh->avahi_poll = avahi_simple_poll_new()))
430 logger(mesh, MESHLINK_ERROR, "Failed to create discovery poll object.\n");
434 // generate some unique host name (we actually do not care about it)
436 uuid_generate(hostname);
438 char hostnamestr[36+1];
439 uuid_unparse_lower(hostname, hostnamestr);
441 // Let's set the host name for this server.
442 AvahiServerConfig config;
443 avahi_server_config_init(&config);
444 config.host_name = avahi_strdup(hostnamestr);
445 config.publish_workstation = 0;
446 config.disallow_other_stacks = 0;
447 config.publish_hinfo = 0;
448 config.publish_addresses = 1;
449 config.publish_no_reverse = 1;
451 /* Allocate a new server */
453 mesh->avahi_server = avahi_server_new(avahi_simple_poll_get(mesh->avahi_poll), &config, discovery_server_callback, mesh, &error);
455 /* Free the configuration data */
456 avahi_server_config_free(&config);
458 /* Check wether creating the server object succeeded */
459 if(!mesh->avahi_server)
461 logger(mesh, MESHLINK_ERROR, "Failed to create discovery server: %s\n", avahi_strerror(error));
465 // Create the service browser
466 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)))
468 logger(mesh, MESHLINK_ERROR, "Failed to create discovery service browser: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
472 // Start the discovery thread
473 if(pthread_create(&mesh->discovery_thread, NULL, discovery_loop, mesh) != 0)
475 logger(mesh, MESHLINK_ERROR, "Could not start discovery thread: %s\n", strerror(errno));
476 memset(&mesh->discovery_thread, 0, sizeof mesh->discovery_thread);
480 mesh->discovery_threadstarted = true;
485 if(mesh->avahi_browser != NULL)
487 avahi_s_service_browser_free(mesh->avahi_browser);
488 mesh->avahi_browser = NULL;
491 if(mesh->avahi_server != NULL)
493 avahi_server_free(mesh->avahi_server);
494 mesh->avahi_server = NULL;
497 if(mesh->avahi_poll != NULL)
499 avahi_simple_poll_free(mesh->avahi_poll);
500 mesh->avahi_poll = NULL;
503 if(mesh->avahi_servicetype != NULL)
505 free(mesh->avahi_servicetype);
506 mesh->avahi_servicetype = NULL;
512 void discovery_stop(meshlink_handle_t *mesh)
514 logger(mesh, MESHLINK_DEBUG, "discovery_stop called\n");
517 assert(mesh != NULL);
522 avahi_simple_poll_quit(mesh->avahi_poll);
525 // Wait for the discovery thread to finish
526 if(mesh->discovery_threadstarted == true)
528 pthread_join(mesh->discovery_thread, NULL);
529 mesh->discovery_threadstarted = false;
532 // Clean up resources
533 if(mesh->avahi_browser != NULL)
535 avahi_s_service_browser_free(mesh->avahi_browser);
536 mesh->avahi_browser = NULL;
539 if(mesh->avahi_server != NULL)
541 avahi_server_free(mesh->avahi_server);
542 mesh->avahi_server = NULL;
545 if(mesh->avahi_poll != NULL)
547 avahi_simple_poll_free(mesh->avahi_poll);
548 mesh->avahi_poll = NULL;
551 if(mesh->avahi_servicetype != NULL)
553 free(mesh->avahi_servicetype);
554 mesh->avahi_servicetype = NULL;