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);
171 case AVAHI_SERVER_REGISTERING:
173 /* Let's drop our registered services. When the server is back
174 * in AVAHI_SERVER_RUNNING state we will register them
175 * again with the new host name. */
176 if(mesh->avahi_group)
178 avahi_s_entry_group_reset(mesh->avahi_group);
179 mesh->avahi_group = NULL;
184 case AVAHI_SERVER_FAILURE:
187 assert(mesh->avahi_server != NULL);
188 assert(mesh->avahi_poll != NULL);
190 /* Terminate on failure */
191 logger(mesh, MESHLINK_ERROR, "Avahi server failure: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
192 avahi_simple_poll_quit(mesh->avahi_poll);
196 case AVAHI_SERVER_INVALID:
201 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)
203 meshlink_handle_t *mesh = userdata;
206 assert(resolver != NULL);
207 assert(mesh != NULL);
208 assert(mesh->avahi_server != NULL);
210 /* Called whenever a service has been resolved successfully or timed out */
213 case AVAHI_RESOLVER_FAILURE:
216 assert(name != NULL);
217 assert(type != NULL);
218 assert(domain != NULL);
220 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)));
224 case AVAHI_RESOLVER_FOUND:
227 assert(name != NULL);
228 assert(type != NULL);
229 assert(domain != NULL);
230 assert(host_name != NULL);
231 assert(address != NULL);
234 char straddr[AVAHI_ADDRESS_STR_MAX], *strtxt;
236 logger(mesh, MESHLINK_DEBUG, "(Resolver) Service '%s' of type '%s' in domain '%s':\n", name, type, domain);
238 avahi_address_snprint(straddr, sizeof(straddr), address);
239 strtxt = avahi_string_list_to_string(txt);
240 logger(mesh, MESHLINK_DEBUG,
248 host_name, port, straddr,
250 avahi_string_list_get_service_cookie(txt),
251 !!(flags & AVAHI_LOOKUP_RESULT_LOCAL),
252 !!(flags & AVAHI_LOOKUP_RESULT_WIDE_AREA),
253 !!(flags & AVAHI_LOOKUP_RESULT_MULTICAST),
254 !!(flags & AVAHI_LOOKUP_RESULT_CACHED));
257 // retrieve fingerprint
258 AvahiStringList *node_name_li = avahi_string_list_find(txt, MESHLINK_MDNS_NAME_KEY);
259 AvahiStringList *node_fp_li = avahi_string_list_find(txt, MESHLINK_MDNS_FINGERPRINT_KEY);
261 if(node_name_li != NULL && node_fp_li != NULL)
263 char *node_name = (char*)avahi_string_list_get_text(node_name_li) + strlen(MESHLINK_MDNS_NAME_KEY);
264 char *node_fp = (char*)avahi_string_list_get_text(node_fp_li) + strlen(MESHLINK_MDNS_FINGERPRINT_KEY);
266 if(node_name[0] == '=' && node_fp[0] == '=')
271 meshlink_node_t *node = meshlink_get_node(mesh, node_name);
275 logger(mesh, MESHLINK_INFO, "Node %s is part of the mesh network.\n", node->name);
278 memset(&naddress, 0, sizeof(naddress));
280 switch(address->proto)
282 case AVAHI_PROTO_INET:
284 naddress.in.sin_family = AF_INET;
285 naddress.in.sin_port = htons(port);
286 naddress.in.sin_addr.s_addr = address->data.ipv4.address;
290 case AVAHI_PROTO_INET6:
292 naddress.in6.sin6_family = AF_INET6;
293 naddress.in6.sin6_port = htons(port);
294 memcpy(naddress.in6.sin6_addr.s6_addr, address->data.ipv6.address, sizeof(naddress.in6.sin6_addr.s6_addr));
299 naddress.unknown.family = AF_UNKNOWN;
303 if(naddress.unknown.family != AF_UNKNOWN)
305 meshlink_hint_address(mesh, (meshlink_node_t *)node, (struct sockaddr*)&naddress);
309 logger(mesh, MESHLINK_WARNING, "Could not resolve node %s to a known address family type.\n", node->name);
314 logger(mesh, MESHLINK_WARNING, "Node %s is not part of the mesh network.\n", node_name);
319 logger(mesh, MESHLINK_WARNING, "TXT records invalid.\n");
324 logger(mesh, MESHLINK_WARNING, "TXT records missing.\n");
330 avahi_s_service_resolver_free(resolver);
333 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)
335 meshlink_handle_t *mesh = userdata;
338 assert(mesh != NULL);
339 assert(mesh->avahi_server != NULL);
340 assert(mesh->avahi_poll != NULL);
342 /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
345 case AVAHI_BROWSER_FAILURE:
347 logger(mesh, MESHLINK_ERROR, "(Browser) %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
348 avahi_simple_poll_quit(mesh->avahi_poll);
352 case AVAHI_BROWSER_NEW:
355 assert(name != NULL);
356 assert(type != NULL);
357 assert(domain != NULL);
359 logger(mesh, MESHLINK_DEBUG, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
360 /* We ignore the returned resolver object. In the callback
361 function we free it. Ifthe server is terminated before
362 the callback function is called the server will free
363 the resolver for us. */
364 if(!(avahi_s_service_resolver_new(mesh->avahi_server, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, discovery_resolve_callback, mesh)))
366 logger(mesh, MESHLINK_DEBUG, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_server_errno(mesh->avahi_server)));
371 case AVAHI_BROWSER_REMOVE:
374 assert(name != NULL);
375 assert(type != NULL);
376 assert(domain != NULL);
378 logger(mesh, MESHLINK_DEBUG, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
382 case AVAHI_BROWSER_ALL_FOR_NOW:
383 case AVAHI_BROWSER_CACHE_EXHAUSTED:
385 logger(mesh, MESHLINK_DEBUG, "(Browser) %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
391 static void *discovery_loop(void *userdata)
393 meshlink_handle_t *mesh = userdata;
396 assert(mesh != NULL);
397 assert(mesh->avahi_poll != NULL);
399 avahi_simple_poll_loop(mesh->avahi_poll);
404 static void discovery_log_cb(AvahiLogLevel level, const char *txt)
406 meshlink_log_level_t mlevel = MESHLINK_CRITICAL;
410 case AVAHI_LOG_ERROR:
411 mlevel = MESHLINK_ERROR;
415 mlevel = MESHLINK_WARNING;
418 case AVAHI_LOG_NOTICE:
420 mlevel = MESHLINK_INFO;
423 case AVAHI_LOG_DEBUG:
424 mlevel = MESHLINK_DEBUG;
428 logger(NULL, mlevel, "%s\n", txt);
431 bool discovery_start(meshlink_handle_t *mesh)
433 logger(mesh, MESHLINK_DEBUG, "discovery_start called\n");
436 assert(mesh != NULL);
437 assert(mesh->avahi_poll == NULL);
438 assert(mesh->avahi_server == NULL);
439 assert(mesh->avahi_browser == NULL);
440 assert(mesh->discovery_threadstarted == false);
441 assert(mesh->avahi_servicetype == NULL);
444 avahi_set_log_function(discovery_log_cb);
446 // create service type string
447 size_t servicetype_strlen = sizeof(MESHLINK_MDNS_SERVICE_TYPE) + strlen(mesh->appname) + 1;
448 mesh->avahi_servicetype = malloc(servicetype_strlen);
450 if(mesh->avahi_servicetype == NULL)
452 logger(mesh, MESHLINK_ERROR, "Failed to allocate memory for service type string.\n");
456 snprintf(mesh->avahi_servicetype, servicetype_strlen, MESHLINK_MDNS_SERVICE_TYPE, mesh->appname);
458 // Allocate discovery loop object
459 if(!(mesh->avahi_poll = avahi_simple_poll_new()))
461 logger(mesh, MESHLINK_ERROR, "Failed to create discovery poll object.\n");
465 // generate some unique host name (we actually do not care about it)
467 uuid_generate(hostname);
469 char hostnamestr[36+1];
470 uuid_unparse_lower(hostname, hostnamestr);
472 // Let's set the host name for this server.
473 AvahiServerConfig config;
474 avahi_server_config_init(&config);
475 config.host_name = avahi_strdup(hostnamestr);
476 config.publish_workstation = 0;
477 config.disallow_other_stacks = 0;
478 config.publish_hinfo = 0;
479 config.publish_addresses = 1;
480 config.publish_no_reverse = 1;
482 /* Allocate a new server */
484 mesh->avahi_server = avahi_server_new(avahi_simple_poll_get(mesh->avahi_poll), &config, discovery_server_callback, mesh, &error);
486 /* Free the configuration data */
487 avahi_server_config_free(&config);
489 /* Check wether creating the server object succeeded */
490 if(!mesh->avahi_server)
492 logger(mesh, MESHLINK_ERROR, "Failed to create discovery server: %s\n", avahi_strerror(error));
496 // Create the service browser
497 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)))
499 logger(mesh, MESHLINK_ERROR, "Failed to create discovery service browser: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
503 // Start the discovery thread
504 if(pthread_create(&mesh->discovery_thread, NULL, discovery_loop, mesh) != 0)
506 logger(mesh, MESHLINK_ERROR, "Could not start discovery thread: %s\n", strerror(errno));
507 memset(&mesh->discovery_thread, 0, sizeof mesh->discovery_thread);
511 mesh->discovery_threadstarted = true;
516 if(mesh->avahi_browser != NULL)
518 avahi_s_service_browser_free(mesh->avahi_browser);
519 mesh->avahi_browser = NULL;
522 if(mesh->avahi_server != NULL)
524 avahi_server_free(mesh->avahi_server);
525 mesh->avahi_server = NULL;
528 if(mesh->avahi_poll != NULL)
530 avahi_simple_poll_free(mesh->avahi_poll);
531 mesh->avahi_poll = NULL;
534 if(mesh->avahi_servicetype != NULL)
536 free(mesh->avahi_servicetype);
537 mesh->avahi_servicetype = NULL;
543 void discovery_stop(meshlink_handle_t *mesh)
545 logger(mesh, MESHLINK_DEBUG, "discovery_stop called\n");
548 assert(mesh != NULL);
553 avahi_simple_poll_quit(mesh->avahi_poll);
556 // Wait for the discovery thread to finish
557 if(mesh->discovery_threadstarted == true)
559 pthread_join(mesh->discovery_thread, NULL);
560 mesh->discovery_threadstarted = false;
563 // Clean up resources
564 if(mesh->avahi_browser != NULL)
566 avahi_s_service_browser_free(mesh->avahi_browser);
567 mesh->avahi_browser = NULL;
570 if(mesh->avahi_group)
572 avahi_s_entry_group_reset(mesh->avahi_group);
573 avahi_s_entry_group_free(mesh->avahi_group);
574 mesh->avahi_group = NULL;
577 if(mesh->avahi_server != NULL)
579 avahi_server_free(mesh->avahi_server);
580 mesh->avahi_server = NULL;
583 if(mesh->avahi_poll != NULL)
585 avahi_simple_poll_free(mesh->avahi_poll);
586 mesh->avahi_poll = NULL;
589 if(mesh->avahi_servicetype != NULL)
591 free(mesh->avahi_servicetype);
592 mesh->avahi_servicetype = NULL;