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_FINGERPRINT_KEY "fingerprint"
23 static void discovery_entry_group_callback(AvahiServer *server, AvahiSEntryGroup *group, AvahiEntryGroupState state, void *userdata)
25 meshlink_handle_t *mesh = userdata;
29 assert(mesh->name != NULL);
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 '%s' successfully established.\n", mesh->name);
41 case AVAHI_ENTRY_GROUP_COLLISION:
42 fprintf(stderr, "Service name collision '%s'\n", mesh->name);
45 case AVAHI_ENTRY_GROUP_FAILURE :
46 /* Some kind of failure happened while we were registering our services */
47 fprintf(stderr, "Entry group failure: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
48 avahi_simple_poll_quit(mesh->avahi_poll);
51 case AVAHI_ENTRY_GROUP_UNCOMMITED:
52 case AVAHI_ENTRY_GROUP_REGISTERING:
58 static void discovery_create_services(meshlink_handle_t *mesh)
62 assert(mesh->name != NULL);
63 assert(mesh->myport != NULL);
64 assert(mesh->avahi_server != NULL);
65 assert(mesh->avahi_poll != NULL);
67 fprintf(stderr, "Adding service '%s'\n", mesh->name);
69 /* Ifthis is the first time we're called, let's create a new entry group */
70 if(!mesh->avahi_group)
72 if(!(mesh->avahi_group = avahi_s_entry_group_new(mesh->avahi_server, discovery_entry_group_callback, mesh)))
74 fprintf(stderr, "avahi_entry_group_new() failed: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
79 /* Create txt record */
80 char txt_fingerprint[MESHLINK_FINGERPRINTLEN + sizeof(MESHLINK_MDNS_FINGERPRINT_KEY) + 2];
81 snprintf(txt_fingerprint, sizeof(txt_fingerprint), "%s=%s", MESHLINK_MDNS_FINGERPRINT_KEY, meshlink_get_fingerprint(mesh, meshlink_get_node(mesh, mesh->name)));
85 if((ret = avahi_server_add_service(mesh->avahi_server, mesh->avahi_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, mesh->name, MESHLINK_MDNS_SERVICE_TYPE, NULL, NULL, atoi(mesh->myport), txt_fingerprint, NULL)) < 0)
87 fprintf(stderr, "Failed to add service: %s\n", avahi_strerror(ret));
91 /* Tell the server to register the service */
92 if((ret = avahi_s_entry_group_commit(mesh->avahi_group)) < 0)
94 fprintf(stderr, "Failed to commit entry_group: %s\n", avahi_strerror(ret));
101 avahi_simple_poll_quit(mesh->avahi_poll);
104 static void discovery_server_callback(AvahiServer *server, AvahiServerState state, void * userdata)
106 meshlink_handle_t *mesh = userdata;
109 assert(mesh != NULL);
113 case AVAHI_SERVER_RUNNING:
115 /* The serve has startup successfully and registered its host
116 * name on the network, so it's time to create our services */
117 if(!mesh->avahi_group)
119 discovery_create_services(mesh);
124 case AVAHI_SERVER_COLLISION:
127 assert(mesh->avahi_server != NULL);
128 assert(mesh->avahi_poll != NULL);
130 fprintf(stderr, "Host name collision with '%s'\n", avahi_server_get_host_name(mesh->avahi_server));
132 /* A host name collision happened. Let's pick a new name for the server */
134 char *new_name = avahi_alternative_host_name(avahi_server_get_host_name(mesh->avahi_server));
135 fprintf(stderr, "Host name collision, retrying with '%s'\n", new_name);
136 int result = avahi_server_set_host_name(mesh->avahi_server, new_name);
137 avahi_free(new_name);
141 fprintf(stderr, "Failed to set new host name: %s\n", avahi_strerror(result));
142 avahi_simple_poll_quit(mesh->avahi_poll);
149 case AVAHI_SERVER_REGISTERING:
151 /* Let's drop our registered services. When the server is back
152 * in AVAHI_SERVER_RUNNING state we will register them
153 * again with the new host name. */
154 if(mesh->avahi_group)
156 avahi_s_entry_group_reset(mesh->avahi_group);
157 mesh->avahi_group = NULL;
162 case AVAHI_SERVER_FAILURE:
165 assert(mesh->avahi_server != NULL);
166 assert(mesh->avahi_poll != NULL);
168 /* Terminate on failure */
169 fprintf(stderr, "Server failure: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
170 avahi_simple_poll_quit(mesh->avahi_poll);
174 case AVAHI_SERVER_INVALID:
179 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)
181 meshlink_handle_t *mesh = userdata;
184 assert(resolver != NULL);
185 assert(mesh != NULL);
186 assert(mesh->avahi_server != NULL);
188 /* Called whenever a service has been resolved successfully or timed out */
191 case AVAHI_RESOLVER_FAILURE:
194 assert(name != NULL);
195 assert(type != NULL);
196 assert(domain != NULL);
198 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)));
202 case AVAHI_RESOLVER_FOUND:
205 assert(name != NULL);
206 assert(type != NULL);
207 assert(domain != NULL);
208 assert(host_name != NULL);
209 assert(address != NULL);
212 char straddr[AVAHI_ADDRESS_STR_MAX], *strtxt;
214 fprintf(stderr, "(Resolver) Service '%s' of type '%s' in domain '%s':\n", name, type, domain);
216 avahi_address_snprint(straddr, sizeof(straddr), address);
217 strtxt = avahi_string_list_to_string(txt);
226 host_name, port, straddr,
228 avahi_string_list_get_service_cookie(txt),
229 !!(flags & AVAHI_LOOKUP_RESULT_LOCAL),
230 !!(flags & AVAHI_LOOKUP_RESULT_WIDE_AREA),
231 !!(flags & AVAHI_LOOKUP_RESULT_MULTICAST),
232 !!(flags & AVAHI_LOOKUP_RESULT_CACHED));
235 // retrieve fingerprint
236 AvahiStringList *fgli = avahi_string_list_find(txt, MESHLINK_MDNS_FINGERPRINT_KEY);
237 meshlink_node_t *node = meshlink_get_node(mesh, name);
239 fprintf(stderr, "%p, %p, %s, %s\n", fgli, node, avahi_string_list_get_text(fgli), meshlink_get_fingerprint(mesh, node));
241 if(node && fgli && strcmp(avahi_string_list_get_text(fgli) + strlen(MESHLINK_MDNS_FINGERPRINT_KEY) + 1, meshlink_get_fingerprint(mesh, node)) == 0 )
243 fprintf(stderr, "Node %s is part of the mesh network.\n", node->name);
246 memset(&naddress, 0, sizeof(naddress));
248 switch(address->proto)
250 case AVAHI_PROTO_INET:
252 naddress.in.sin_family = AF_INET;
253 naddress.in.sin_port = port;
254 naddress.in.sin_addr.s_addr = address->data.ipv4.address;
258 case AVAHI_PROTO_INET6:
260 naddress.in6.sin6_family = AF_INET6;
261 naddress.in6.sin6_port = port;
262 memcpy(naddress.in6.sin6_addr.s6_addr, address->data.ipv6.address, sizeof(naddress.in6.sin6_addr.s6_addr));
267 naddress.unknown.family = AF_UNKNOWN;
271 if(naddress.unknown.family != AF_UNKNOWN)
273 meshlink_hint_address(mesh, node, (struct sockaddr*)&naddress);
277 fprintf(stderr, "Could not resolve node %s to a known address family type.\n", node->name);
282 fprintf(stderr, "Node %s is not part of the mesh network.\n", node ? node->name : "n/a");
288 avahi_s_service_resolver_free(resolver);
291 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)
293 meshlink_handle_t *mesh = userdata;
296 assert(mesh != NULL);
297 assert(mesh->avahi_server != NULL);
298 assert(mesh->avahi_poll != NULL);
300 /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
303 case AVAHI_BROWSER_FAILURE:
305 fprintf(stderr, "(Browser) %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
306 avahi_simple_poll_quit(mesh->avahi_poll);
310 case AVAHI_BROWSER_NEW:
313 assert(name != NULL);
314 assert(type != NULL);
315 assert(domain != NULL);
317 fprintf(stderr, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
318 /* We ignore the returned resolver object. In the callback
319 function we free it. Ifthe server is terminated before
320 the callback function is called the server will free
321 the resolver for us. */
322 if(!(avahi_s_service_resolver_new(mesh->avahi_server, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, discovery_resolve_callback, mesh)))
324 fprintf(stderr, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_server_errno(mesh->avahi_server)));
329 case AVAHI_BROWSER_REMOVE:
332 assert(name != NULL);
333 assert(type != NULL);
334 assert(domain != NULL);
336 fprintf(stderr, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
340 case AVAHI_BROWSER_ALL_FOR_NOW:
341 case AVAHI_BROWSER_CACHE_EXHAUSTED:
343 fprintf(stderr, "(Browser) %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
349 static void *discovery_loop(void *userdata)
351 meshlink_handle_t *mesh = userdata;
354 assert(mesh != NULL);
355 assert(mesh->avahi_poll != NULL);
357 avahi_simple_poll_loop(mesh->avahi_poll);
362 bool discovery_start(meshlink_handle_t *mesh)
365 assert(mesh != NULL);
366 assert(mesh->avahi_poll == NULL);
367 assert(mesh->avahi_server == NULL);
368 assert(mesh->avahi_browser == NULL);
369 assert(mesh->discovery_threadstarted == false);
371 // Allocate discovery loop object
372 if(!(mesh->avahi_poll = avahi_simple_poll_new()))
374 fprintf(stderr, "Failed to create discovery poll object.\n");
378 // generate random host name
380 uuid_generate(hostname);
382 char hostnamestr[36+1];
383 uuid_unparse_lower(hostname, hostnamestr);
385 // Let's set the host name for this server.
386 AvahiServerConfig config;
387 avahi_server_config_init(&config);
388 config.host_name = avahi_strdup(hostnamestr);
389 config.publish_workstation = 0;
390 config.disallow_other_stacks = 0;
391 config.publish_hinfo = 0;
393 /* Allocate a new server */
395 mesh->avahi_server = avahi_server_new(avahi_simple_poll_get(mesh->avahi_poll), &config, discovery_server_callback, mesh, &error);
397 /* Free the configuration data */
398 avahi_server_config_free(&config);
400 /* Check wether creating the server object succeeded */
401 if(!mesh->avahi_server)
403 fprintf(stderr, "Failed to create discovery server: %s\n", avahi_strerror(error));
407 // Create the service browser
408 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)))
410 fprintf(stderr, "Failed to create discovery service browser: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
414 // Start the discovery thread
415 if(pthread_create(&mesh->discovery_thread, NULL, discovery_loop, mesh) != 0)
417 fprintf(stderr, "Could not start discovery thread: %s\n", strerror(errno));
418 memset(&mesh->discovery_thread, 0, sizeof mesh->discovery_thread);
422 mesh->discovery_threadstarted = true;
427 if(mesh->avahi_browser)
429 avahi_s_service_browser_free(mesh->avahi_browser);
430 mesh->avahi_browser = NULL;
433 if(mesh->avahi_server)
435 avahi_server_free(mesh->avahi_server);
436 mesh->avahi_server = NULL;
441 avahi_simple_poll_free(mesh->avahi_poll);
442 mesh->avahi_poll = NULL;
448 void discovery_stop(meshlink_handle_t *mesh)
451 assert(mesh != NULL);
452 assert(mesh->avahi_poll != NULL);
453 assert(mesh->avahi_server != NULL);
454 assert(mesh->avahi_browser != NULL);
455 assert(mesh->discovery_threadstarted == true);
458 avahi_simple_poll_quit(mesh->avahi_poll);
460 // Wait for the discovery thread to finish
461 pthread_join(mesh->discovery_thread, NULL);
463 // Clean up resources
464 avahi_s_service_browser_free(mesh->avahi_browser);
465 mesh->avahi_browser = NULL;
467 avahi_server_free(mesh->avahi_server);
468 mesh->avahi_server = NULL;
470 avahi_simple_poll_free(mesh->avahi_poll);
471 mesh->avahi_poll = NULL;