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);
70 assert(mesh->self != NULL);
72 fprintf(stderr, "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 fprintf(stderr, "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 fprintf(stderr, "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 fprintf(stderr, "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 fprintf(stderr, "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 fprintf(stderr, "Host name collision, retrying with '%s'\n", hostnamestr);
158 int result = avahi_server_set_host_name(mesh->avahi_server, hostnamestr);
162 fprintf(stderr, "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 fprintf(stderr, "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 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)));
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 fprintf(stderr, "(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);
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 = avahi_string_list_get_text(node_name_li) + strlen(MESHLINK_MDNS_NAME_KEY) + 1;
262 char *node_fp = avahi_string_list_get_text(node_fp_li) + strlen(MESHLINK_MDNS_FINGERPRINT_KEY) + 1;
264 meshlink_node_t *node = meshlink_get_node(mesh, node_name);
268 fprintf(stderr, "Node %s is part of the mesh network.\n", node->name);
271 memset(&naddress, 0, sizeof(naddress));
273 switch(address->proto)
275 case AVAHI_PROTO_INET:
277 naddress.in.sin_family = AF_INET;
278 naddress.in.sin_port = port;
279 naddress.in.sin_addr.s_addr = address->data.ipv4.address;
283 case AVAHI_PROTO_INET6:
285 naddress.in6.sin6_family = AF_INET6;
286 naddress.in6.sin6_port = port;
287 memcpy(naddress.in6.sin6_addr.s6_addr, address->data.ipv6.address, sizeof(naddress.in6.sin6_addr.s6_addr));
292 naddress.unknown.family = AF_UNKNOWN;
296 if(naddress.unknown.family != AF_UNKNOWN)
298 meshlink_hint_address(mesh, (meshlink_node_t *)node, (struct sockaddr*)&naddress);
302 fprintf(stderr, "Could not resolve node %s to a known address family type.\n", node->name);
307 fprintf(stderr, "Node %s is not part of the mesh network.\n", node_name);
312 fprintf(stderr, "TXT records missing.\n");
318 avahi_s_service_resolver_free(resolver);
321 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)
323 meshlink_handle_t *mesh = userdata;
326 assert(mesh != NULL);
327 assert(mesh->avahi_server != NULL);
328 assert(mesh->avahi_poll != NULL);
330 /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
333 case AVAHI_BROWSER_FAILURE:
335 fprintf(stderr, "(Browser) %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
336 avahi_simple_poll_quit(mesh->avahi_poll);
340 case AVAHI_BROWSER_NEW:
343 assert(name != NULL);
344 assert(type != NULL);
345 assert(domain != NULL);
347 fprintf(stderr, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
348 /* We ignore the returned resolver object. In the callback
349 function we free it. Ifthe server is terminated before
350 the callback function is called the server will free
351 the resolver for us. */
352 if(!(avahi_s_service_resolver_new(mesh->avahi_server, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, discovery_resolve_callback, mesh)))
354 fprintf(stderr, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_server_errno(mesh->avahi_server)));
359 case AVAHI_BROWSER_REMOVE:
362 assert(name != NULL);
363 assert(type != NULL);
364 assert(domain != NULL);
366 fprintf(stderr, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
370 case AVAHI_BROWSER_ALL_FOR_NOW:
371 case AVAHI_BROWSER_CACHE_EXHAUSTED:
373 fprintf(stderr, "(Browser) %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
379 static void *discovery_loop(void *userdata)
381 meshlink_handle_t *mesh = userdata;
384 assert(mesh != NULL);
385 assert(mesh->avahi_poll != NULL);
387 avahi_simple_poll_loop(mesh->avahi_poll);
392 bool discovery_start(meshlink_handle_t *mesh)
395 assert(mesh != NULL);
396 assert(mesh->avahi_poll == NULL);
397 assert(mesh->avahi_server == NULL);
398 assert(mesh->avahi_browser == NULL);
399 assert(mesh->discovery_threadstarted == false);
400 assert(mesh->avahi_servicetype == NULL);
402 // create service type string
403 size_t servicetype_strlen = sizeof(MESHLINK_MDNS_SERVICE_TYPE) + strlen(mesh->appname) + 1;
404 mesh->avahi_servicetype = malloc(servicetype_strlen);
406 if(mesh->avahi_servicetype == NULL)
408 fprintf(stderr, "Failed to allocate memory for service type string.\n");
412 snprintf(mesh->avahi_servicetype, servicetype_strlen, MESHLINK_MDNS_SERVICE_TYPE, mesh->appname);
414 // Allocate discovery loop object
415 if(!(mesh->avahi_poll = avahi_simple_poll_new()))
417 fprintf(stderr, "Failed to create discovery poll object.\n");
421 // generate some unique host name (we actually do not care about it)
423 uuid_generate(hostname);
425 char hostnamestr[36+1];
426 uuid_unparse_lower(hostname, hostnamestr);
428 // Let's set the host name for this server.
429 AvahiServerConfig config;
430 avahi_server_config_init(&config);
431 config.host_name = avahi_strdup(hostnamestr);
432 config.publish_workstation = 0;
433 config.disallow_other_stacks = 0;
434 config.publish_hinfo = 0;
435 config.publish_addresses = 1;
436 config.publish_no_reverse = 1;
438 /* Allocate a new server */
440 mesh->avahi_server = avahi_server_new(avahi_simple_poll_get(mesh->avahi_poll), &config, discovery_server_callback, mesh, &error);
442 /* Free the configuration data */
443 avahi_server_config_free(&config);
445 /* Check wether creating the server object succeeded */
446 if(!mesh->avahi_server)
448 fprintf(stderr, "Failed to create discovery server: %s\n", avahi_strerror(error));
452 // Create the service browser
453 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)))
455 fprintf(stderr, "Failed to create discovery service browser: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
459 // Start the discovery thread
460 if(pthread_create(&mesh->discovery_thread, NULL, discovery_loop, mesh) != 0)
462 fprintf(stderr, "Could not start discovery thread: %s\n", strerror(errno));
463 memset(&mesh->discovery_thread, 0, sizeof mesh->discovery_thread);
467 mesh->discovery_threadstarted = true;
472 if(mesh->avahi_browser != NULL)
474 avahi_s_service_browser_free(mesh->avahi_browser);
475 mesh->avahi_browser = NULL;
478 if(mesh->avahi_server != NULL)
480 avahi_server_free(mesh->avahi_server);
481 mesh->avahi_server = NULL;
484 if(mesh->avahi_poll != NULL)
486 avahi_simple_poll_free(mesh->avahi_poll);
487 mesh->avahi_poll = NULL;
490 if(mesh->avahi_servicetype != NULL)
492 free(mesh->avahi_servicetype);
493 mesh->avahi_servicetype = NULL;
499 void discovery_stop(meshlink_handle_t *mesh)
502 assert(mesh != NULL);
507 avahi_simple_poll_quit(mesh->avahi_poll);
510 // Wait for the discovery thread to finish
511 if(mesh->discovery_threadstarted == true)
513 pthread_join(mesh->discovery_thread, NULL);
514 mesh->discovery_threadstarted = false;
517 // Clean up resources
518 if(mesh->avahi_browser != NULL)
520 avahi_s_service_browser_free(mesh->avahi_browser);
521 mesh->avahi_browser = NULL;
524 if(mesh->avahi_server != NULL)
526 avahi_server_free(mesh->avahi_server);
527 mesh->avahi_server = NULL;
530 if(mesh->avahi_poll != NULL)
532 avahi_simple_poll_free(mesh->avahi_poll);
533 mesh->avahi_poll = NULL;
536 if(mesh->avahi_servicetype != NULL)
538 free(mesh->avahi_servicetype);
539 mesh->avahi_servicetype = NULL;