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 fprintf(stderr, "Service successfully established.\n");
41 case AVAHI_ENTRY_GROUP_COLLISION:
42 fprintf(stderr, "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 fprintf(stderr, "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);
71 fprintf(stderr, "Adding service\n");
73 /* Ifthis is the first time we're called, let's create a new entry group */
74 if(!mesh->avahi_group)
76 if(!(mesh->avahi_group = avahi_s_entry_group_new(mesh->avahi_server, discovery_entry_group_callback, mesh)))
78 fprintf(stderr, "avahi_entry_group_new() failed: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
83 /* Create txt records */
84 size_t txt_name_len = sizeof(MESHLINK_MDNS_NAME_KEY) + 1 + strlen(mesh->name) + 1;
85 txt_name = malloc(txt_name_len);
86 snprintf(txt_name, txt_name_len, "%s=%s", MESHLINK_MDNS_NAME_KEY, mesh->name);
88 char txt_fingerprint[sizeof(MESHLINK_MDNS_FINGERPRINT_KEY) + 1 + MESHLINK_FINGERPRINTLEN + 1];
89 snprintf(txt_fingerprint, sizeof(txt_fingerprint), "%s=%s", MESHLINK_MDNS_FINGERPRINT_KEY, meshlink_get_fingerprint(mesh, (meshlink_node_t *)mesh->self));
91 // Generate a name for the service (actually we do not care)
93 uuid_generate(srvname);
95 char srvnamestr[36+1];
96 uuid_unparse_lower(srvname, srvnamestr);
100 if((ret = avahi_server_add_service(mesh->avahi_server, mesh->avahi_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, srvnamestr, mesh->avahi_servicetype, NULL, NULL, atoi(mesh->myport), txt_name, txt_fingerprint, NULL)) < 0)
102 fprintf(stderr, "Failed to add service: %s\n", avahi_strerror(ret));
106 /* Tell the server to register the service */
107 if((ret = avahi_s_entry_group_commit(mesh->avahi_group)) < 0)
109 fprintf(stderr, "Failed to commit entry_group: %s\n", avahi_strerror(ret));
116 avahi_simple_poll_quit(mesh->avahi_poll);
123 static void discovery_server_callback(AvahiServer *server, AvahiServerState state, void * userdata)
125 meshlink_handle_t *mesh = userdata;
128 assert(mesh != NULL);
132 case AVAHI_SERVER_RUNNING:
134 /* The serve has startup successfully and registered its host
135 * name on the network, so it's time to create our services */
136 if(!mesh->avahi_group)
138 discovery_create_services(mesh);
143 case AVAHI_SERVER_COLLISION:
146 assert(mesh->avahi_server != NULL);
147 assert(mesh->avahi_poll != NULL);
149 /* A host name collision happened. Let's pick a new name for the server */
151 uuid_generate(hostname);
153 char hostnamestr[36+1];
154 uuid_unparse_lower(hostname, hostnamestr);
156 fprintf(stderr, "Host name collision, retrying with '%s'\n", hostnamestr);
157 int result = avahi_server_set_host_name(mesh->avahi_server, hostnamestr);
161 fprintf(stderr, "Failed to set new host name: %s\n", avahi_strerror(result));
162 avahi_simple_poll_quit(mesh->avahi_poll);
168 case AVAHI_SERVER_REGISTERING:
170 /* Let's drop our registered services. When the server is back
171 * in AVAHI_SERVER_RUNNING state we will register them
172 * again with the new host name. */
173 if(mesh->avahi_group)
175 avahi_s_entry_group_reset(mesh->avahi_group);
176 mesh->avahi_group = NULL;
181 case AVAHI_SERVER_FAILURE:
184 assert(mesh->avahi_server != NULL);
185 assert(mesh->avahi_poll != NULL);
187 /* Terminate on failure */
188 fprintf(stderr, "Server failure: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
189 avahi_simple_poll_quit(mesh->avahi_poll);
193 case AVAHI_SERVER_INVALID:
198 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)
200 meshlink_handle_t *mesh = userdata;
203 assert(resolver != NULL);
204 assert(mesh != NULL);
205 assert(mesh->avahi_server != NULL);
207 /* Called whenever a service has been resolved successfully or timed out */
210 case AVAHI_RESOLVER_FAILURE:
213 assert(name != NULL);
214 assert(type != NULL);
215 assert(domain != NULL);
217 fprintf(stderr, "(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)));
221 case AVAHI_RESOLVER_FOUND:
224 assert(name != NULL);
225 assert(type != NULL);
226 assert(domain != NULL);
227 assert(host_name != NULL);
228 assert(address != NULL);
231 char straddr[AVAHI_ADDRESS_STR_MAX], *strtxt;
233 fprintf(stderr, "(Resolver) Service '%s' of type '%s' in domain '%s':\n", name, type, domain);
235 avahi_address_snprint(straddr, sizeof(straddr), address);
236 strtxt = avahi_string_list_to_string(txt);
245 host_name, port, straddr,
247 avahi_string_list_get_service_cookie(txt),
248 !!(flags & AVAHI_LOOKUP_RESULT_LOCAL),
249 !!(flags & AVAHI_LOOKUP_RESULT_WIDE_AREA),
250 !!(flags & AVAHI_LOOKUP_RESULT_MULTICAST),
251 !!(flags & AVAHI_LOOKUP_RESULT_CACHED));
254 // retrieve fingerprint
255 AvahiStringList *node_name_li = avahi_string_list_find(txt, MESHLINK_MDNS_NAME_KEY);
256 AvahiStringList *node_fp_li = avahi_string_list_find(txt, MESHLINK_MDNS_FINGERPRINT_KEY);
258 if(node_name_li != NULL && node_fp_li != NULL)
260 char *node_name = avahi_string_list_get_text(node_name_li) + strlen(MESHLINK_MDNS_NAME_KEY) + 1;
261 char *node_fp = avahi_string_list_get_text(node_fp_li) + strlen(MESHLINK_MDNS_FINGERPRINT_KEY) + 1;
263 meshlink_node_t *node = meshlink_get_node(mesh, node_name);
267 fprintf(stderr, "Node %s is part of the mesh network.\n", node->name);
270 memset(&naddress, 0, sizeof(naddress));
272 switch(address->proto)
274 case AVAHI_PROTO_INET:
276 naddress.in.sin_family = AF_INET;
277 naddress.in.sin_port = port;
278 naddress.in.sin_addr.s_addr = address->data.ipv4.address;
282 case AVAHI_PROTO_INET6:
284 naddress.in6.sin6_family = AF_INET6;
285 naddress.in6.sin6_port = port;
286 memcpy(naddress.in6.sin6_addr.s6_addr, address->data.ipv6.address, sizeof(naddress.in6.sin6_addr.s6_addr));
291 naddress.unknown.family = AF_UNKNOWN;
295 if(naddress.unknown.family != AF_UNKNOWN)
297 meshlink_hint_address(mesh, (meshlink_node_t *)node, (struct sockaddr*)&naddress);
301 fprintf(stderr, "Could not resolve node %s to a known address family type.\n", node->name);
306 fprintf(stderr, "Node %s is not part of the mesh network.\n", node_name);
311 fprintf(stderr, "TXT records missing.\n");
317 avahi_s_service_resolver_free(resolver);
320 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)
322 meshlink_handle_t *mesh = userdata;
325 assert(mesh != NULL);
326 assert(mesh->avahi_server != NULL);
327 assert(mesh->avahi_poll != NULL);
329 /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
332 case AVAHI_BROWSER_FAILURE:
334 fprintf(stderr, "(Browser) %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
335 avahi_simple_poll_quit(mesh->avahi_poll);
339 case AVAHI_BROWSER_NEW:
342 assert(name != NULL);
343 assert(type != NULL);
344 assert(domain != NULL);
346 fprintf(stderr, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
347 /* We ignore the returned resolver object. In the callback
348 function we free it. Ifthe server is terminated before
349 the callback function is called the server will free
350 the resolver for us. */
351 if(!(avahi_s_service_resolver_new(mesh->avahi_server, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, discovery_resolve_callback, mesh)))
353 fprintf(stderr, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_server_errno(mesh->avahi_server)));
358 case AVAHI_BROWSER_REMOVE:
361 assert(name != NULL);
362 assert(type != NULL);
363 assert(domain != NULL);
365 fprintf(stderr, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
369 case AVAHI_BROWSER_ALL_FOR_NOW:
370 case AVAHI_BROWSER_CACHE_EXHAUSTED:
372 fprintf(stderr, "(Browser) %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
378 static void *discovery_loop(void *userdata)
380 meshlink_handle_t *mesh = userdata;
383 assert(mesh != NULL);
384 assert(mesh->avahi_poll != NULL);
386 avahi_simple_poll_loop(mesh->avahi_poll);
391 bool discovery_start(meshlink_handle_t *mesh)
394 assert(mesh != NULL);
395 assert(mesh->avahi_poll == NULL);
396 assert(mesh->avahi_server == NULL);
397 assert(mesh->avahi_browser == NULL);
398 assert(mesh->discovery_threadstarted == false);
399 assert(mesh->avahi_servicetype == NULL);
401 // create service type string
402 size_t servicetype_strlen = sizeof(MESHLINK_MDNS_SERVICE_TYPE) + strlen(mesh->appname) + 1;
403 mesh->avahi_servicetype = malloc(servicetype_strlen);
405 if(mesh->avahi_servicetype == NULL)
407 fprintf(stderr, "Failed to allocate memory for service type string.\n");
411 snprintf(mesh->avahi_servicetype, servicetype_strlen, MESHLINK_MDNS_SERVICE_TYPE, mesh->appname);
413 // Allocate discovery loop object
414 if(!(mesh->avahi_poll = avahi_simple_poll_new()))
416 fprintf(stderr, "Failed to create discovery poll object.\n");
420 // generate some unique host name (we actually do not care about it)
422 uuid_generate(hostname);
424 char hostnamestr[36+1];
425 uuid_unparse_lower(hostname, hostnamestr);
427 // Let's set the host name for this server.
428 AvahiServerConfig config;
429 avahi_server_config_init(&config);
430 config.host_name = avahi_strdup(hostnamestr);
431 config.publish_workstation = 0;
432 config.disallow_other_stacks = 0;
433 config.publish_hinfo = 0;
434 config.publish_addresses = 1;
435 config.publish_no_reverse = 1;
437 /* Allocate a new server */
439 mesh->avahi_server = avahi_server_new(avahi_simple_poll_get(mesh->avahi_poll), &config, discovery_server_callback, mesh, &error);
441 /* Free the configuration data */
442 avahi_server_config_free(&config);
444 /* Check wether creating the server object succeeded */
445 if(!mesh->avahi_server)
447 fprintf(stderr, "Failed to create discovery server: %s\n", avahi_strerror(error));
451 // Create the service browser
452 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)))
454 fprintf(stderr, "Failed to create discovery service browser: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
458 // Start the discovery thread
459 if(pthread_create(&mesh->discovery_thread, NULL, discovery_loop, mesh) != 0)
461 fprintf(stderr, "Could not start discovery thread: %s\n", strerror(errno));
462 memset(&mesh->discovery_thread, 0, sizeof mesh->discovery_thread);
466 mesh->discovery_threadstarted = true;
471 if(mesh->avahi_browser != NULL)
473 avahi_s_service_browser_free(mesh->avahi_browser);
474 mesh->avahi_browser = NULL;
477 if(mesh->avahi_server != NULL)
479 avahi_server_free(mesh->avahi_server);
480 mesh->avahi_server = NULL;
483 if(mesh->avahi_poll != NULL)
485 avahi_simple_poll_free(mesh->avahi_poll);
486 mesh->avahi_poll = NULL;
489 if(mesh->avahi_servicetype != NULL)
491 free(mesh->avahi_servicetype);
492 mesh->avahi_servicetype = NULL;
498 void discovery_stop(meshlink_handle_t *mesh)
501 assert(mesh != NULL);
502 assert(mesh->avahi_poll != NULL);
503 assert(mesh->avahi_server != NULL);
504 assert(mesh->avahi_browser != NULL);
505 assert(mesh->discovery_threadstarted == true);
506 assert(mesh->avahi_servicetype != NULL);
509 avahi_simple_poll_quit(mesh->avahi_poll);
511 // Wait for the discovery thread to finish
512 pthread_join(mesh->discovery_thread, NULL);
514 // Clean up resources
515 avahi_s_service_browser_free(mesh->avahi_browser);
516 mesh->avahi_browser = NULL;
518 avahi_server_free(mesh->avahi_server);
519 mesh->avahi_server = NULL;
521 avahi_simple_poll_free(mesh->avahi_poll);
522 mesh->avahi_poll = NULL;
524 free(mesh->avahi_servicetype);
525 mesh->avahi_servicetype = NULL;