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);
89 fprintf(stderr, "Could not allocate memory for TXT record\n");
93 snprintf(txt_name, txt_name_len, "%s=%s", MESHLINK_MDNS_NAME_KEY, mesh->name);
95 char txt_fingerprint[sizeof(MESHLINK_MDNS_FINGERPRINT_KEY) + 1 + MESHLINK_FINGERPRINTLEN + 1];
96 snprintf(txt_fingerprint, sizeof(txt_fingerprint), "%s=%s", MESHLINK_MDNS_FINGERPRINT_KEY, meshlink_get_fingerprint(mesh, (meshlink_node_t *)mesh->self));
98 // Generate a name for the service (actually we do not care)
100 uuid_generate(srvname);
102 char srvnamestr[36+1];
103 uuid_unparse_lower(srvname, srvnamestr);
105 /* Add the service */
107 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)
109 fprintf(stderr, "Failed to add service: %s\n", avahi_strerror(ret));
113 /* Tell the server to register the service */
114 if((ret = avahi_s_entry_group_commit(mesh->avahi_group)) < 0)
116 fprintf(stderr, "Failed to commit entry_group: %s\n", avahi_strerror(ret));
123 avahi_simple_poll_quit(mesh->avahi_poll);
130 static void discovery_server_callback(AvahiServer *server, AvahiServerState state, void * userdata)
132 meshlink_handle_t *mesh = userdata;
135 assert(mesh != NULL);
139 case AVAHI_SERVER_RUNNING:
141 /* The serve has startup successfully and registered its host
142 * name on the network, so it's time to create our services */
143 if(!mesh->avahi_group)
145 discovery_create_services(mesh);
150 case AVAHI_SERVER_COLLISION:
153 assert(mesh->avahi_server != NULL);
154 assert(mesh->avahi_poll != NULL);
156 /* A host name collision happened. Let's pick a new name for the server */
158 uuid_generate(hostname);
160 char hostnamestr[36+1];
161 uuid_unparse_lower(hostname, hostnamestr);
163 fprintf(stderr, "Host name collision, retrying with '%s'\n", hostnamestr);
164 int result = avahi_server_set_host_name(mesh->avahi_server, hostnamestr);
168 fprintf(stderr, "Failed to set new host name: %s\n", avahi_strerror(result));
169 avahi_simple_poll_quit(mesh->avahi_poll);
175 case AVAHI_SERVER_REGISTERING:
177 /* Let's drop our registered services. When the server is back
178 * in AVAHI_SERVER_RUNNING state we will register them
179 * again with the new host name. */
180 if(mesh->avahi_group)
182 avahi_s_entry_group_reset(mesh->avahi_group);
183 mesh->avahi_group = NULL;
188 case AVAHI_SERVER_FAILURE:
191 assert(mesh->avahi_server != NULL);
192 assert(mesh->avahi_poll != NULL);
194 /* Terminate on failure */
195 fprintf(stderr, "Server failure: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
196 avahi_simple_poll_quit(mesh->avahi_poll);
200 case AVAHI_SERVER_INVALID:
205 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)
207 meshlink_handle_t *mesh = userdata;
210 assert(resolver != NULL);
211 assert(mesh != NULL);
212 assert(mesh->avahi_server != NULL);
214 /* Called whenever a service has been resolved successfully or timed out */
217 case AVAHI_RESOLVER_FAILURE:
220 assert(name != NULL);
221 assert(type != NULL);
222 assert(domain != NULL);
224 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)));
228 case AVAHI_RESOLVER_FOUND:
231 assert(name != NULL);
232 assert(type != NULL);
233 assert(domain != NULL);
234 assert(host_name != NULL);
235 assert(address != NULL);
238 char straddr[AVAHI_ADDRESS_STR_MAX], *strtxt;
240 fprintf(stderr, "(Resolver) Service '%s' of type '%s' in domain '%s':\n", name, type, domain);
242 avahi_address_snprint(straddr, sizeof(straddr), address);
243 strtxt = avahi_string_list_to_string(txt);
252 host_name, port, straddr,
254 avahi_string_list_get_service_cookie(txt),
255 !!(flags & AVAHI_LOOKUP_RESULT_LOCAL),
256 !!(flags & AVAHI_LOOKUP_RESULT_WIDE_AREA),
257 !!(flags & AVAHI_LOOKUP_RESULT_MULTICAST),
258 !!(flags & AVAHI_LOOKUP_RESULT_CACHED));
261 // retrieve fingerprint
262 AvahiStringList *node_name_li = avahi_string_list_find(txt, MESHLINK_MDNS_NAME_KEY);
263 AvahiStringList *node_fp_li = avahi_string_list_find(txt, MESHLINK_MDNS_FINGERPRINT_KEY);
265 if(node_name_li != NULL && node_fp_li != NULL)
267 char *node_name = avahi_string_list_get_text(node_name_li) + strlen(MESHLINK_MDNS_NAME_KEY) + 1;
268 char *node_fp = avahi_string_list_get_text(node_fp_li) + strlen(MESHLINK_MDNS_FINGERPRINT_KEY) + 1;
270 meshlink_node_t *node = meshlink_get_node(mesh, node_name);
274 fprintf(stderr, "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 fprintf(stderr, "Could not resolve node %s to a known address family type.\n", node->name);
313 fprintf(stderr, "Node %s is not part of the mesh network.\n", node_name);
318 fprintf(stderr, "TXT records missing.\n");
324 avahi_s_service_resolver_free(resolver);
327 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)
329 meshlink_handle_t *mesh = userdata;
332 assert(mesh != NULL);
333 assert(mesh->avahi_server != NULL);
334 assert(mesh->avahi_poll != NULL);
336 /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
339 case AVAHI_BROWSER_FAILURE:
341 fprintf(stderr, "(Browser) %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
342 avahi_simple_poll_quit(mesh->avahi_poll);
346 case AVAHI_BROWSER_NEW:
349 assert(name != NULL);
350 assert(type != NULL);
351 assert(domain != NULL);
353 fprintf(stderr, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
354 /* We ignore the returned resolver object. In the callback
355 function we free it. Ifthe server is terminated before
356 the callback function is called the server will free
357 the resolver for us. */
358 if(!(avahi_s_service_resolver_new(mesh->avahi_server, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, discovery_resolve_callback, mesh)))
360 fprintf(stderr, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_server_errno(mesh->avahi_server)));
365 case AVAHI_BROWSER_REMOVE:
368 assert(name != NULL);
369 assert(type != NULL);
370 assert(domain != NULL);
372 fprintf(stderr, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
376 case AVAHI_BROWSER_ALL_FOR_NOW:
377 case AVAHI_BROWSER_CACHE_EXHAUSTED:
379 fprintf(stderr, "(Browser) %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
385 static void *discovery_loop(void *userdata)
387 meshlink_handle_t *mesh = userdata;
390 assert(mesh != NULL);
391 assert(mesh->avahi_poll != NULL);
393 avahi_simple_poll_loop(mesh->avahi_poll);
398 bool discovery_start(meshlink_handle_t *mesh)
401 assert(mesh != NULL);
402 assert(mesh->avahi_poll == NULL);
403 assert(mesh->avahi_server == NULL);
404 assert(mesh->avahi_browser == NULL);
405 assert(mesh->discovery_threadstarted == false);
406 assert(mesh->avahi_servicetype == NULL);
408 // create service type string
409 size_t servicetype_strlen = sizeof(MESHLINK_MDNS_SERVICE_TYPE) + strlen(mesh->appname) + 1;
410 mesh->avahi_servicetype = malloc(servicetype_strlen);
412 if(mesh->avahi_servicetype == NULL)
414 fprintf(stderr, "Failed to allocate memory for service type string.\n");
418 snprintf(mesh->avahi_servicetype, servicetype_strlen, MESHLINK_MDNS_SERVICE_TYPE, mesh->appname);
420 // Allocate discovery loop object
421 if(!(mesh->avahi_poll = avahi_simple_poll_new()))
423 fprintf(stderr, "Failed to create discovery poll object.\n");
427 // generate some unique host name (we actually do not care about it)
429 uuid_generate(hostname);
431 char hostnamestr[36+1];
432 uuid_unparse_lower(hostname, hostnamestr);
434 // Let's set the host name for this server.
435 AvahiServerConfig config;
436 avahi_server_config_init(&config);
437 config.host_name = avahi_strdup(hostnamestr);
438 config.publish_workstation = 0;
439 config.disallow_other_stacks = 0;
440 config.publish_hinfo = 0;
441 config.publish_addresses = 1;
442 config.publish_no_reverse = 1;
444 /* Allocate a new server */
446 mesh->avahi_server = avahi_server_new(avahi_simple_poll_get(mesh->avahi_poll), &config, discovery_server_callback, mesh, &error);
448 /* Free the configuration data */
449 avahi_server_config_free(&config);
451 /* Check wether creating the server object succeeded */
452 if(!mesh->avahi_server)
454 fprintf(stderr, "Failed to create discovery server: %s\n", avahi_strerror(error));
458 // Create the service browser
459 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)))
461 fprintf(stderr, "Failed to create discovery service browser: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
465 // Start the discovery thread
466 if(pthread_create(&mesh->discovery_thread, NULL, discovery_loop, mesh) != 0)
468 fprintf(stderr, "Could not start discovery thread: %s\n", strerror(errno));
469 memset(&mesh->discovery_thread, 0, sizeof mesh->discovery_thread);
473 mesh->discovery_threadstarted = true;
478 if(mesh->avahi_browser != NULL)
480 avahi_s_service_browser_free(mesh->avahi_browser);
481 mesh->avahi_browser = NULL;
484 if(mesh->avahi_server != NULL)
486 avahi_server_free(mesh->avahi_server);
487 mesh->avahi_server = NULL;
490 if(mesh->avahi_poll != NULL)
492 avahi_simple_poll_free(mesh->avahi_poll);
493 mesh->avahi_poll = NULL;
496 if(mesh->avahi_servicetype != NULL)
498 free(mesh->avahi_servicetype);
499 mesh->avahi_servicetype = NULL;
505 void discovery_stop(meshlink_handle_t *mesh)
508 assert(mesh != NULL);
509 assert(mesh->avahi_poll != NULL);
510 assert(mesh->avahi_server != NULL);
511 assert(mesh->avahi_browser != NULL);
512 assert(mesh->discovery_threadstarted == true);
513 assert(mesh->avahi_servicetype != NULL);
516 avahi_simple_poll_quit(mesh->avahi_poll);
518 // Wait for the discovery thread to finish
519 pthread_join(mesh->discovery_thread, NULL);
521 // Clean up resources
522 avahi_s_service_browser_free(mesh->avahi_browser);
523 mesh->avahi_browser = NULL;
525 avahi_server_free(mesh->avahi_server);
526 mesh->avahi_server = NULL;
528 avahi_simple_poll_free(mesh->avahi_poll);
529 mesh->avahi_poll = NULL;
531 free(mesh->avahi_servicetype);
532 mesh->avahi_servicetype = NULL;