2 #include "meshlink_internal.h"
8 #include <avahi-core/core.h>
9 #include <avahi-core/lookup.h>
10 #include <avahi-core/publish.h>
11 #include <avahi-common/simple-watch.h>
12 #include <avahi-common/malloc.h>
13 #include <avahi-common/alternative.h>
14 #include <avahi-common/error.h>
16 #include <netinet/in.h>
18 #include <uuid/uuid.h>
20 #define MESHLINK_MDNS_SERVICE_TYPE "_%s._tcp"
21 #define MESHLINK_MDNS_NAME_KEY "name"
22 #define MESHLINK_MDNS_FINGERPRINT_KEY "fingerprint"
24 static void discovery_entry_group_callback(AvahiServer *server, AvahiSEntryGroup *group, AvahiEntryGroupState state, void *userdata)
26 meshlink_handle_t *mesh = userdata;
30 assert(mesh->avahi_server != NULL);
31 assert(mesh->avahi_poll != NULL);
33 /* Called whenever the entry group state changes */
36 case AVAHI_ENTRY_GROUP_ESTABLISHED:
37 /* The entry group has been established successfully */
38 logger(mesh, MESHLINK_DEBUG, "Avahi Service successfully established.\n");
41 case AVAHI_ENTRY_GROUP_COLLISION:
42 logger(mesh, MESHLINK_WARNING, "Avahi Service collision.\n");
43 // @TODO can we just set a new name and retry?
46 case AVAHI_ENTRY_GROUP_FAILURE :
47 /* Some kind of failure happened while we were registering our services */
48 logger(mesh, MESHLINK_ERROR, "Avahi Entry group failure: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
49 avahi_simple_poll_quit(mesh->avahi_poll);
52 case AVAHI_ENTRY_GROUP_UNCOMMITED:
53 case AVAHI_ENTRY_GROUP_REGISTERING:
59 static void discovery_create_services(meshlink_handle_t *mesh)
61 char *txt_name = NULL;
65 assert(mesh->name != NULL);
66 assert(mesh->myport != NULL);
67 assert(mesh->avahi_server != NULL);
68 assert(mesh->avahi_poll != NULL);
69 assert(mesh->avahi_servicetype != NULL);
70 assert(mesh->self != NULL);
72 logger(mesh, MESHLINK_DEBUG, "Adding service\n");
74 /* Ifthis is the first time we're called, let's create a new entry group */
75 if(!mesh->avahi_group)
77 if(!(mesh->avahi_group = avahi_s_entry_group_new(mesh->avahi_server, discovery_entry_group_callback, mesh)))
79 logger(mesh, MESHLINK_ERROR, "avahi_entry_group_new() failed: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
84 /* Create txt records */
85 size_t txt_name_len = sizeof(MESHLINK_MDNS_NAME_KEY) + 1 + strlen(mesh->name) + 1;
86 txt_name = malloc(txt_name_len);
90 logger(mesh, MESHLINK_ERROR, "Could not allocate memory for TXT record\n");
94 snprintf(txt_name, txt_name_len, "%s=%s", MESHLINK_MDNS_NAME_KEY, mesh->name);
96 char txt_fingerprint[sizeof(MESHLINK_MDNS_FINGERPRINT_KEY) + 1 + MESHLINK_FINGERPRINTLEN + 1];
97 snprintf(txt_fingerprint, sizeof(txt_fingerprint), "%s=%s", MESHLINK_MDNS_FINGERPRINT_KEY, meshlink_get_fingerprint(mesh, (meshlink_node_t *)mesh->self));
101 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)
103 logger(mesh, MESHLINK_ERROR, "Failed to add service: %s\n", avahi_strerror(ret));
107 /* Tell the server to register the service */
108 if((ret = avahi_s_entry_group_commit(mesh->avahi_group)) < 0)
110 logger(mesh, MESHLINK_ERROR, "Failed to commit entry_group: %s\n", avahi_strerror(ret));
117 avahi_simple_poll_quit(mesh->avahi_poll);
124 static void discovery_server_callback(AvahiServer *server, AvahiServerState state, void * userdata)
126 meshlink_handle_t *mesh = userdata;
129 assert(mesh != NULL);
133 case AVAHI_SERVER_RUNNING:
135 /* The serve has startup successfully and registered its host
136 * name on the network, so it's time to create our services */
137 if(!mesh->avahi_group)
139 discovery_create_services(mesh);
144 case AVAHI_SERVER_COLLISION:
147 assert(mesh->avahi_server != NULL);
148 assert(mesh->avahi_poll != NULL);
150 /* A host name collision happened. Let's pick a new name for the server */
152 uuid_generate(hostname);
154 char hostnamestr[36+1];
155 uuid_unparse_lower(hostname, hostnamestr);
157 logger(mesh, MESHLINK_WARNING, "Avahi host name collision, retrying with '%s'\n", hostnamestr);
158 int result = avahi_server_set_host_name(mesh->avahi_server, hostnamestr);
162 logger(mesh, MESHLINK_ERROR, "Avahi failed to set new host name: %s\n", avahi_strerror(result));
163 avahi_simple_poll_quit(mesh->avahi_poll);
169 case AVAHI_SERVER_REGISTERING:
171 /* Let's drop our registered services. When the server is back
172 * in AVAHI_SERVER_RUNNING state we will register them
173 * again with the new host name. */
174 if(mesh->avahi_group)
176 avahi_s_entry_group_reset(mesh->avahi_group);
177 mesh->avahi_group = NULL;
182 case AVAHI_SERVER_FAILURE:
185 assert(mesh->avahi_server != NULL);
186 assert(mesh->avahi_poll != NULL);
188 /* Terminate on failure */
189 logger(mesh, MESHLINK_ERROR, "Avahi server failure: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
190 avahi_simple_poll_quit(mesh->avahi_poll);
194 case AVAHI_SERVER_INVALID:
199 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)
201 meshlink_handle_t *mesh = userdata;
204 assert(resolver != NULL);
205 assert(mesh != NULL);
206 assert(mesh->avahi_server != NULL);
208 /* Called whenever a service has been resolved successfully or timed out */
211 case AVAHI_RESOLVER_FAILURE:
214 assert(name != NULL);
215 assert(type != NULL);
216 assert(domain != NULL);
218 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)));
222 case AVAHI_RESOLVER_FOUND:
225 assert(name != NULL);
226 assert(type != NULL);
227 assert(domain != NULL);
228 assert(host_name != NULL);
229 assert(address != NULL);
232 char straddr[AVAHI_ADDRESS_STR_MAX], *strtxt;
234 logger(mesh, MESHLINK_DEBUG, "(Resolver) Service '%s' of type '%s' in domain '%s':\n", name, type, domain);
236 avahi_address_snprint(straddr, sizeof(straddr), address);
237 strtxt = avahi_string_list_to_string(txt);
238 logger(mesh, MESHLINK_DEBUG,
246 host_name, port, straddr,
248 avahi_string_list_get_service_cookie(txt),
249 !!(flags & AVAHI_LOOKUP_RESULT_LOCAL),
250 !!(flags & AVAHI_LOOKUP_RESULT_WIDE_AREA),
251 !!(flags & AVAHI_LOOKUP_RESULT_MULTICAST),
252 !!(flags & AVAHI_LOOKUP_RESULT_CACHED));
255 // retrieve fingerprint
256 AvahiStringList *node_name_li = avahi_string_list_find(txt, MESHLINK_MDNS_NAME_KEY);
257 AvahiStringList *node_fp_li = avahi_string_list_find(txt, MESHLINK_MDNS_FINGERPRINT_KEY);
259 if(node_name_li != NULL && node_fp_li != NULL)
261 char *node_name = (char*)avahi_string_list_get_text(node_name_li) + strlen(MESHLINK_MDNS_NAME_KEY);
262 char *node_fp = (char*)avahi_string_list_get_text(node_fp_li) + strlen(MESHLINK_MDNS_FINGERPRINT_KEY);
264 if(node_name[0] == '=' && node_fp[0] == '=')
269 meshlink_node_t *node = meshlink_get_node(mesh, node_name);
273 logger(mesh, MESHLINK_INFO, "Node %s is part of the mesh network.\n", node->name);
276 memset(&naddress, 0, sizeof(naddress));
278 switch(address->proto)
280 case AVAHI_PROTO_INET:
282 naddress.in.sin_family = AF_INET;
283 naddress.in.sin_port = port;
284 naddress.in.sin_addr.s_addr = address->data.ipv4.address;
288 case AVAHI_PROTO_INET6:
290 naddress.in6.sin6_family = AF_INET6;
291 naddress.in6.sin6_port = port;
292 memcpy(naddress.in6.sin6_addr.s6_addr, address->data.ipv6.address, sizeof(naddress.in6.sin6_addr.s6_addr));
297 naddress.unknown.family = AF_UNKNOWN;
301 if(naddress.unknown.family != AF_UNKNOWN)
303 meshlink_hint_address(mesh, (meshlink_node_t *)node, (struct sockaddr*)&naddress);
307 logger(mesh, MESHLINK_WARNING, "Could not resolve node %s to a known address family type.\n", node->name);
312 logger(mesh, MESHLINK_WARNING, "Node %s is not part of the mesh network.\n", node_name);
317 logger(mesh, MESHLINK_WARNING, "TXT records invalid.\n");
322 logger(mesh, MESHLINK_WARNING, "TXT records missing.\n");
328 avahi_s_service_resolver_free(resolver);
331 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)
333 meshlink_handle_t *mesh = userdata;
336 assert(mesh != NULL);
337 assert(mesh->avahi_server != NULL);
338 assert(mesh->avahi_poll != NULL);
340 /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
343 case AVAHI_BROWSER_FAILURE:
345 logger(mesh, MESHLINK_ERROR, "(Browser) %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
346 avahi_simple_poll_quit(mesh->avahi_poll);
350 case AVAHI_BROWSER_NEW:
353 assert(name != NULL);
354 assert(type != NULL);
355 assert(domain != NULL);
357 logger(mesh, MESHLINK_DEBUG, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
358 /* We ignore the returned resolver object. In the callback
359 function we free it. Ifthe server is terminated before
360 the callback function is called the server will free
361 the resolver for us. */
362 if(!(avahi_s_service_resolver_new(mesh->avahi_server, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, discovery_resolve_callback, mesh)))
364 logger(mesh, MESHLINK_DEBUG, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_server_errno(mesh->avahi_server)));
369 case AVAHI_BROWSER_REMOVE:
372 assert(name != NULL);
373 assert(type != NULL);
374 assert(domain != NULL);
376 logger(mesh, MESHLINK_DEBUG, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
380 case AVAHI_BROWSER_ALL_FOR_NOW:
381 case AVAHI_BROWSER_CACHE_EXHAUSTED:
383 logger(mesh, MESHLINK_DEBUG, "(Browser) %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
389 static void *discovery_loop(void *userdata)
391 meshlink_handle_t *mesh = userdata;
394 assert(mesh != NULL);
395 assert(mesh->avahi_poll != NULL);
397 avahi_simple_poll_loop(mesh->avahi_poll);
402 bool discovery_start(meshlink_handle_t *mesh)
404 logger(mesh, MESHLINK_DEBUG, "discovery_start called\n");
407 assert(mesh != NULL);
408 assert(mesh->avahi_poll == NULL);
409 assert(mesh->avahi_server == NULL);
410 assert(mesh->avahi_browser == NULL);
411 assert(mesh->discovery_threadstarted == false);
412 assert(mesh->avahi_servicetype == NULL);
414 // create service type string
415 size_t servicetype_strlen = sizeof(MESHLINK_MDNS_SERVICE_TYPE) + strlen(mesh->appname) + 1;
416 mesh->avahi_servicetype = malloc(servicetype_strlen);
418 if(mesh->avahi_servicetype == NULL)
420 logger(mesh, MESHLINK_ERROR, "Failed to allocate memory for service type string.\n");
424 snprintf(mesh->avahi_servicetype, servicetype_strlen, MESHLINK_MDNS_SERVICE_TYPE, mesh->appname);
426 // Allocate discovery loop object
427 if(!(mesh->avahi_poll = avahi_simple_poll_new()))
429 logger(mesh, MESHLINK_ERROR, "Failed to create discovery poll object.\n");
433 // generate some unique host name (we actually do not care about it)
435 uuid_generate(hostname);
437 char hostnamestr[36+1];
438 uuid_unparse_lower(hostname, hostnamestr);
440 // Let's set the host name for this server.
441 AvahiServerConfig config;
442 avahi_server_config_init(&config);
443 config.host_name = avahi_strdup(hostnamestr);
444 config.publish_workstation = 0;
445 config.disallow_other_stacks = 0;
446 config.publish_hinfo = 0;
447 config.publish_addresses = 1;
448 config.publish_no_reverse = 1;
450 /* Allocate a new server */
452 mesh->avahi_server = avahi_server_new(avahi_simple_poll_get(mesh->avahi_poll), &config, discovery_server_callback, mesh, &error);
454 /* Free the configuration data */
455 avahi_server_config_free(&config);
457 /* Check wether creating the server object succeeded */
458 if(!mesh->avahi_server)
460 logger(mesh, MESHLINK_ERROR, "Failed to create discovery server: %s\n", avahi_strerror(error));
464 // Create the service browser
465 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)))
467 logger(mesh, MESHLINK_ERROR, "Failed to create discovery service browser: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
471 // Start the discovery thread
472 if(pthread_create(&mesh->discovery_thread, NULL, discovery_loop, mesh) != 0)
474 logger(mesh, MESHLINK_ERROR, "Could not start discovery thread: %s\n", strerror(errno));
475 memset(&mesh->discovery_thread, 0, sizeof mesh->discovery_thread);
479 mesh->discovery_threadstarted = true;
484 if(mesh->avahi_browser != NULL)
486 avahi_s_service_browser_free(mesh->avahi_browser);
487 mesh->avahi_browser = NULL;
490 if(mesh->avahi_server != NULL)
492 avahi_server_free(mesh->avahi_server);
493 mesh->avahi_server = NULL;
496 if(mesh->avahi_poll != NULL)
498 avahi_simple_poll_free(mesh->avahi_poll);
499 mesh->avahi_poll = NULL;
502 if(mesh->avahi_servicetype != NULL)
504 free(mesh->avahi_servicetype);
505 mesh->avahi_servicetype = NULL;
511 void discovery_stop(meshlink_handle_t *mesh)
513 logger(mesh, MESHLINK_DEBUG, "discovery_stop called\n");
516 assert(mesh != NULL);
521 avahi_simple_poll_quit(mesh->avahi_poll);
524 // Wait for the discovery thread to finish
525 if(mesh->discovery_threadstarted == true)
527 pthread_join(mesh->discovery_thread, NULL);
528 mesh->discovery_threadstarted = false;
531 // Clean up resources
532 if(mesh->avahi_browser != NULL)
534 avahi_s_service_browser_free(mesh->avahi_browser);
535 mesh->avahi_browser = NULL;
538 if(mesh->avahi_server != NULL)
540 avahi_server_free(mesh->avahi_server);
541 mesh->avahi_server = NULL;
544 if(mesh->avahi_poll != NULL)
546 avahi_simple_poll_free(mesh->avahi_poll);
547 mesh->avahi_poll = NULL;
550 if(mesh->avahi_servicetype != NULL)
552 free(mesh->avahi_servicetype);
553 mesh->avahi_servicetype = NULL;