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 "_meshlink._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);
70 fprintf(stderr, "Adding service\n");
72 /* Ifthis is the first time we're called, let's create a new entry group */
73 if(!mesh->avahi_group)
75 if(!(mesh->avahi_group = avahi_s_entry_group_new(mesh->avahi_server, discovery_entry_group_callback, mesh)))
77 fprintf(stderr, "avahi_entry_group_new() failed: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
82 /* Create txt records */
83 size_t txt_name_len = sizeof(MESHLINK_MDNS_NAME_KEY) + 1 + strlen(mesh->name) + 1;
84 txt_name = malloc(txt_name_len);
85 snprintf(txt_name, txt_name_len, "%s=%s", MESHLINK_MDNS_NAME_KEY, mesh->name);
87 char txt_fingerprint[sizeof(MESHLINK_MDNS_FINGERPRINT_KEY) + 1 + MESHLINK_FINGERPRINTLEN + 1];
88 snprintf(txt_fingerprint, sizeof(txt_fingerprint), "%s=%s", MESHLINK_MDNS_FINGERPRINT_KEY, meshlink_get_fingerprint(mesh, (meshlink_node_t *)mesh->self));
90 // Generate a name for the service (actually we do not care)
92 uuid_generate(srvname);
94 char srvnamestr[36+1];
95 uuid_unparse_lower(srvname, srvnamestr);
99 if((ret = avahi_server_add_service(mesh->avahi_server, mesh->avahi_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, srvnamestr, MESHLINK_MDNS_SERVICE_TYPE, NULL, NULL, atoi(mesh->myport), txt_name, txt_fingerprint, NULL)) < 0)
101 fprintf(stderr, "Failed to add service: %s\n", avahi_strerror(ret));
105 /* Tell the server to register the service */
106 if((ret = avahi_s_entry_group_commit(mesh->avahi_group)) < 0)
108 fprintf(stderr, "Failed to commit entry_group: %s\n", avahi_strerror(ret));
115 avahi_simple_poll_quit(mesh->avahi_poll);
122 static void discovery_server_callback(AvahiServer *server, AvahiServerState state, void * userdata)
124 meshlink_handle_t *mesh = userdata;
127 assert(mesh != NULL);
131 case AVAHI_SERVER_RUNNING:
133 /* The serve has startup successfully and registered its host
134 * name on the network, so it's time to create our services */
135 if(!mesh->avahi_group)
137 discovery_create_services(mesh);
142 case AVAHI_SERVER_COLLISION:
145 assert(mesh->avahi_server != NULL);
146 assert(mesh->avahi_poll != NULL);
148 /* A host name collision happened. Let's pick a new name for the server */
150 uuid_generate(hostname);
152 char hostnamestr[36+1];
153 uuid_unparse_lower(hostname, hostnamestr);
155 fprintf(stderr, "Host name collision, retrying with '%s'\n", hostnamestr);
156 int result = avahi_server_set_host_name(mesh->avahi_server, hostnamestr);
160 fprintf(stderr, "Failed to set new host name: %s\n", avahi_strerror(result));
161 avahi_simple_poll_quit(mesh->avahi_poll);
167 case AVAHI_SERVER_REGISTERING:
169 /* Let's drop our registered services. When the server is back
170 * in AVAHI_SERVER_RUNNING state we will register them
171 * again with the new host name. */
172 if(mesh->avahi_group)
174 avahi_s_entry_group_reset(mesh->avahi_group);
175 mesh->avahi_group = NULL;
180 case AVAHI_SERVER_FAILURE:
183 assert(mesh->avahi_server != NULL);
184 assert(mesh->avahi_poll != NULL);
186 /* Terminate on failure */
187 fprintf(stderr, "Server failure: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
188 avahi_simple_poll_quit(mesh->avahi_poll);
192 case AVAHI_SERVER_INVALID:
197 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)
199 meshlink_handle_t *mesh = userdata;
202 assert(resolver != NULL);
203 assert(mesh != NULL);
204 assert(mesh->avahi_server != NULL);
206 /* Called whenever a service has been resolved successfully or timed out */
209 case AVAHI_RESOLVER_FAILURE:
212 assert(name != NULL);
213 assert(type != NULL);
214 assert(domain != NULL);
216 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)));
220 case AVAHI_RESOLVER_FOUND:
223 assert(name != NULL);
224 assert(type != NULL);
225 assert(domain != NULL);
226 assert(host_name != NULL);
227 assert(address != NULL);
230 char straddr[AVAHI_ADDRESS_STR_MAX], *strtxt;
232 fprintf(stderr, "(Resolver) Service '%s' of type '%s' in domain '%s':\n", name, type, domain);
234 avahi_address_snprint(straddr, sizeof(straddr), address);
235 strtxt = avahi_string_list_to_string(txt);
244 host_name, port, straddr,
246 avahi_string_list_get_service_cookie(txt),
247 !!(flags & AVAHI_LOOKUP_RESULT_LOCAL),
248 !!(flags & AVAHI_LOOKUP_RESULT_WIDE_AREA),
249 !!(flags & AVAHI_LOOKUP_RESULT_MULTICAST),
250 !!(flags & AVAHI_LOOKUP_RESULT_CACHED));
253 // retrieve fingerprint
254 AvahiStringList *node_name_li = avahi_string_list_find(txt, MESHLINK_MDNS_NAME_KEY);
255 AvahiStringList *node_fp_li = avahi_string_list_find(txt, MESHLINK_MDNS_FINGERPRINT_KEY);
257 if(node_name_li != NULL && node_fp_li != NULL)
259 char *node_name = avahi_string_list_get_text(node_name_li) + strlen(MESHLINK_MDNS_NAME_KEY) + 1;
260 char *node_fp = avahi_string_list_get_text(node_fp_li) + strlen(MESHLINK_MDNS_FINGERPRINT_KEY) + 1;
262 meshlink_node_t *node = meshlink_get_node(mesh, node_name);
266 fprintf(stderr, "Node %s is part of the mesh network.\n", node->name);
269 memset(&naddress, 0, sizeof(naddress));
271 switch(address->proto)
273 case AVAHI_PROTO_INET:
275 naddress.in.sin_family = AF_INET;
276 naddress.in.sin_port = port;
277 naddress.in.sin_addr.s_addr = address->data.ipv4.address;
281 case AVAHI_PROTO_INET6:
283 naddress.in6.sin6_family = AF_INET6;
284 naddress.in6.sin6_port = port;
285 memcpy(naddress.in6.sin6_addr.s6_addr, address->data.ipv6.address, sizeof(naddress.in6.sin6_addr.s6_addr));
290 naddress.unknown.family = AF_UNKNOWN;
294 if(naddress.unknown.family != AF_UNKNOWN)
296 meshlink_hint_address(mesh, (meshlink_node_t *)node, (struct sockaddr*)&naddress);
300 fprintf(stderr, "Could not resolve node %s to a known address family type.\n", node->name);
305 fprintf(stderr, "Node %s is not part of the mesh network.\n", node_name);
310 fprintf(stderr, "TXT records missing.\n");
316 avahi_s_service_resolver_free(resolver);
319 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)
321 meshlink_handle_t *mesh = userdata;
324 assert(mesh != NULL);
325 assert(mesh->avahi_server != NULL);
326 assert(mesh->avahi_poll != NULL);
328 /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
331 case AVAHI_BROWSER_FAILURE:
333 fprintf(stderr, "(Browser) %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
334 avahi_simple_poll_quit(mesh->avahi_poll);
338 case AVAHI_BROWSER_NEW:
341 assert(name != NULL);
342 assert(type != NULL);
343 assert(domain != NULL);
345 fprintf(stderr, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
346 /* We ignore the returned resolver object. In the callback
347 function we free it. Ifthe server is terminated before
348 the callback function is called the server will free
349 the resolver for us. */
350 if(!(avahi_s_service_resolver_new(mesh->avahi_server, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, discovery_resolve_callback, mesh)))
352 fprintf(stderr, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_server_errno(mesh->avahi_server)));
357 case AVAHI_BROWSER_REMOVE:
360 assert(name != NULL);
361 assert(type != NULL);
362 assert(domain != NULL);
364 fprintf(stderr, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
368 case AVAHI_BROWSER_ALL_FOR_NOW:
369 case AVAHI_BROWSER_CACHE_EXHAUSTED:
371 fprintf(stderr, "(Browser) %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
377 static void *discovery_loop(void *userdata)
379 meshlink_handle_t *mesh = userdata;
382 assert(mesh != NULL);
383 assert(mesh->avahi_poll != NULL);
385 avahi_simple_poll_loop(mesh->avahi_poll);
390 bool discovery_start(meshlink_handle_t *mesh)
393 assert(mesh != NULL);
394 assert(mesh->avahi_poll == NULL);
395 assert(mesh->avahi_server == NULL);
396 assert(mesh->avahi_browser == NULL);
397 assert(mesh->discovery_threadstarted == false);
399 // Allocate discovery loop object
400 if(!(mesh->avahi_poll = avahi_simple_poll_new()))
402 fprintf(stderr, "Failed to create discovery poll object.\n");
406 // generate some unique host name (we actually do not care about it)
408 uuid_generate(hostname);
410 char hostnamestr[36+1];
411 uuid_unparse_lower(hostname, hostnamestr);
413 // Let's set the host name for this server.
414 AvahiServerConfig config;
415 avahi_server_config_init(&config);
416 config.host_name = avahi_strdup(hostnamestr);
417 config.publish_workstation = 0;
418 config.disallow_other_stacks = 0;
419 config.publish_hinfo = 0;
420 config.publish_addresses = 1;
421 config.publish_no_reverse = 1;
423 /* Allocate a new server */
425 mesh->avahi_server = avahi_server_new(avahi_simple_poll_get(mesh->avahi_poll), &config, discovery_server_callback, mesh, &error);
427 /* Free the configuration data */
428 avahi_server_config_free(&config);
430 /* Check wether creating the server object succeeded */
431 if(!mesh->avahi_server)
433 fprintf(stderr, "Failed to create discovery server: %s\n", avahi_strerror(error));
437 // Create the service browser
438 if(!(mesh->avahi_browser = avahi_s_service_browser_new(mesh->avahi_server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, MESHLINK_MDNS_SERVICE_TYPE, NULL, 0, discovery_browse_callback, mesh)))
440 fprintf(stderr, "Failed to create discovery service browser: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
444 // Start the discovery thread
445 if(pthread_create(&mesh->discovery_thread, NULL, discovery_loop, mesh) != 0)
447 fprintf(stderr, "Could not start discovery thread: %s\n", strerror(errno));
448 memset(&mesh->discovery_thread, 0, sizeof mesh->discovery_thread);
452 mesh->discovery_threadstarted = true;
457 if(mesh->avahi_browser)
459 avahi_s_service_browser_free(mesh->avahi_browser);
460 mesh->avahi_browser = NULL;
463 if(mesh->avahi_server)
465 avahi_server_free(mesh->avahi_server);
466 mesh->avahi_server = NULL;
471 avahi_simple_poll_free(mesh->avahi_poll);
472 mesh->avahi_poll = NULL;
478 void discovery_stop(meshlink_handle_t *mesh)
481 assert(mesh != NULL);
482 assert(mesh->avahi_poll != NULL);
483 assert(mesh->avahi_server != NULL);
484 assert(mesh->avahi_browser != NULL);
485 assert(mesh->discovery_threadstarted == true);
488 avahi_simple_poll_quit(mesh->avahi_poll);
490 // Wait for the discovery thread to finish
491 pthread_join(mesh->discovery_thread, NULL);
493 // Clean up resources
494 avahi_s_service_browser_free(mesh->avahi_browser);
495 mesh->avahi_browser = NULL;
497 avahi_server_free(mesh->avahi_server);
498 mesh->avahi_server = NULL;
500 avahi_simple_poll_free(mesh->avahi_poll);
501 mesh->avahi_poll = NULL;