2 #include "meshlink_internal.h"
7 #include <avahi-core/core.h>
8 #include <avahi-core/lookup.h>
9 #include <avahi-core/publish.h>
10 #include <avahi-common/simple-watch.h>
11 #include <avahi-common/malloc.h>
12 #include <avahi-common/alternative.h>
13 #include <avahi-common/error.h>
15 #include <netinet/in.h>
17 #define MESHLINK_MDNS_SERVICE_TYPE "_meshlink._tcp"
18 //#define MESHLINK_MDNS_SERVICE_NAME "Meshlink"
19 #define MESHLINK_MDNS_FINGERPRINT_KEY "fingerprint"
21 static void discovery_resolve_callback(
22 AvahiSServiceResolver *resolver,
23 AVAHI_GCC_UNUSED AvahiIfIndex interface,
24 AVAHI_GCC_UNUSED AvahiProtocol protocol,
25 AvahiResolverEvent event,
29 const char *host_name,
30 const AvahiAddress *address,
33 AvahiLookupResultFlags flags,
34 AVAHI_GCC_UNUSED void* userdata)
37 meshlink_handle_t *mesh = userdata;
39 /* Called whenever a service has been resolved successfully or timed out */
43 case AVAHI_RESOLVER_FAILURE:
44 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)));
47 case AVAHI_RESOLVER_FOUND:
49 char straddr[AVAHI_ADDRESS_STR_MAX], *strtxt;
51 fprintf(stderr, "(Resolver) Service '%s' of type '%s' in domain '%s':\n", name, type, domain);
53 avahi_address_snprint(straddr, sizeof(straddr), address);
54 strtxt = avahi_string_list_to_string(txt);
63 host_name, port, straddr,
65 avahi_string_list_get_service_cookie(txt),
66 !!(flags & AVAHI_LOOKUP_RESULT_LOCAL),
67 !!(flags & AVAHI_LOOKUP_RESULT_WIDE_AREA),
68 !!(flags & AVAHI_LOOKUP_RESULT_MULTICAST),
69 !!(flags & AVAHI_LOOKUP_RESULT_CACHED));
72 // retrieve fingerprint
73 AvahiStringList *fgli = avahi_string_list_find(txt, MESHLINK_MDNS_FINGERPRINT_KEY);
74 meshlink_node_t *node = meshlink_get_node(mesh, name);
76 fprintf(stderr, "%p, %p, %s, %s\n", fgli, node, avahi_string_list_get_text(fgli), meshlink_get_fingerprint(mesh, node));
78 if( node && fgli && strcmp(avahi_string_list_get_text(fgli)+strlen(MESHLINK_MDNS_FINGERPRINT_KEY)+1, meshlink_get_fingerprint(mesh, node)) == 0 )
80 fprintf(stderr, "Node %s is part of the mesh network - updating ip address.\n", node->name);
82 struct sockaddr_storage naddr;
83 memset(&naddr, 0, sizeof(naddr));
85 switch(address->proto)
87 case AVAHI_PROTO_INET:
89 struct sockaddr_in* naddr_in = (struct sockaddr_in*)&naddr;
90 naddr_in->sin_family = AF_INET;
91 naddr_in->sin_port = port;
92 naddr_in->sin_addr.s_addr = address->data.ipv4.address;
96 case AVAHI_PROTO_INET6:
98 struct sockaddr_in6* naddr_in = (struct sockaddr_in6*)&naddr;
99 naddr_in->sin6_family = AF_INET6;
100 naddr_in->sin6_port = port;
101 memcpy(naddr_in->sin6_addr.s6_addr, address->data.ipv6.address, sizeof(naddr_in->sin6_addr.s6_addr));
106 naddr.ss_family = AF_UNKNOWN;
109 // @TODO: aquire mutex?
110 if(naddr.ss_family == AF_INET || naddr.ss_family == AF_INET6)
112 meshlink_hint_address(mesh, node->name, (struct sockaddr*)&naddr);
117 fprintf(stderr, "Node %s is not part of the mesh network - ignoring ip address.\n", node ? node->name : "n/a");
122 avahi_s_service_resolver_free(resolver);
125 static void discovery_entry_group_callback(AvahiServer *server, AvahiSEntryGroup *group, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata)
127 meshlink_handle_t *mesh = userdata;
129 /* Called whenever the entry group state changes */
132 case AVAHI_ENTRY_GROUP_ESTABLISHED:
133 /* The entry group has been established successfully */
134 fprintf(stderr, "Service '%s' successfully established.\n", /*MESHLINK_MDNS_SERVICE_NAME*/ mesh->name);
137 case AVAHI_ENTRY_GROUP_COLLISION:
138 fprintf(stderr, "Service name collision '%s'\n", /*MESHLINK_MDNS_SERVICE_NAME*/ mesh->name);
141 case AVAHI_ENTRY_GROUP_FAILURE :
142 fprintf(stderr, "Entry group failure: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
144 /* Some kind of failure happened while we were registering our services */
145 avahi_simple_poll_quit(mesh->avahi_poll);
148 case AVAHI_ENTRY_GROUP_UNCOMMITED:
149 case AVAHI_ENTRY_GROUP_REGISTERING:
155 static void discovery_create_services(meshlink_handle_t *mesh)
157 fprintf(stderr, "Adding service '%s'\n", /*MESHLINK_MDNS_SERVICE_NAME*/ mesh->name);
159 /* If this is the first time we're called, let's create a new entry group */
160 if (!mesh->avahi_group)
161 if (!(mesh->avahi_group = avahi_s_entry_group_new(mesh->avahi_server, discovery_entry_group_callback, mesh))) {
162 fprintf(stderr, "avahi_entry_group_new() failed: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
166 /* Create some random TXT data */
167 char fingerprint[1024] = "";
168 snprintf(fingerprint, sizeof(fingerprint), "%s=%s", MESHLINK_MDNS_FINGERPRINT_KEY, meshlink_get_fingerprint(mesh, meshlink_get_node(mesh, mesh->name)));
170 /* Add the service for IPP */
172 if ((ret = avahi_server_add_service(mesh->avahi_server, mesh->avahi_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, /*MESHLINK_MDNS_SERVICE_NAME*/ mesh->name, MESHLINK_MDNS_SERVICE_TYPE, NULL, NULL, mesh->myport ? atoi(mesh->myport) : 655, fingerprint, NULL)) < 0) {
173 fprintf(stderr, "Failed to add _ipp._tcp service: %s\n", avahi_strerror(ret));
177 /* Tell the server to register the service */
178 if ((ret = avahi_s_entry_group_commit(mesh->avahi_group)) < 0) {
179 fprintf(stderr, "Failed to commit entry_group: %s\n", avahi_strerror(ret));
186 avahi_simple_poll_quit(mesh->avahi_poll);
189 static void discovery_server_callback(AvahiServer *server, AvahiServerState state, AVAHI_GCC_UNUSED void * userdata)
191 meshlink_handle_t *mesh = userdata;
195 case AVAHI_SERVER_RUNNING:
196 /* The serve has startup successfully and registered its host
197 * name on the network, so it's time to create our services */
198 if (!mesh->avahi_group)
199 discovery_create_services(mesh);
202 case AVAHI_SERVER_COLLISION:
203 /* A host name collision happened. Let's do nothing */
206 case AVAHI_SERVER_REGISTERING:
207 /* Let's drop our registered services. When the server is back
208 * in AVAHI_SERVER_RUNNING state we will register them
209 * again with the new host name. */
210 //if (mesh->avahi_group)
211 // avahi_s_entry_group_reset(mesh->avahi_group);
214 case AVAHI_SERVER_FAILURE:
215 /* Terminate on failure */
216 fprintf(stderr, "Server failure: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
217 avahi_simple_poll_quit(mesh->avahi_poll);
220 case AVAHI_SERVER_INVALID:
225 static void discovery_browse_callback(
226 AvahiSServiceBrowser *browser,
227 AvahiIfIndex interface,
228 AvahiProtocol protocol,
229 AvahiBrowserEvent event,
233 AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
236 meshlink_handle_t *mesh = userdata;
238 /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
242 case AVAHI_BROWSER_FAILURE:
243 fprintf(stderr, "(Browser) %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
244 avahi_simple_poll_quit(mesh->avahi_poll);
247 case AVAHI_BROWSER_NEW:
248 fprintf(stderr, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
249 /* We ignore the returned resolver object. In the callback
250 function we free it. If the server is terminated before
251 the callback function is called the server will free
252 the resolver for us. */
253 if (!(avahi_s_service_resolver_new(mesh->avahi_server, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, discovery_resolve_callback, mesh)))
254 fprintf(stderr, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_server_errno(mesh->avahi_server)));
257 case AVAHI_BROWSER_REMOVE:
258 fprintf(stderr, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
261 case AVAHI_BROWSER_ALL_FOR_NOW:
262 case AVAHI_BROWSER_CACHE_EXHAUSTED:
263 fprintf(stderr, "(Browser) %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
268 static void *discovery_loop(void *arg)
270 meshlink_handle_t *mesh = arg;
272 avahi_simple_poll_loop(mesh->avahi_poll);
277 bool discovery_start(meshlink_handle_t *mesh)
279 // Allocate discovery loop object
280 if (!(mesh->avahi_poll = avahi_simple_poll_new())) {
281 fprintf(stderr, "Failed to create discovery poll object.\n");
285 // Let's set the host name for this server.
286 AvahiServerConfig config;
287 avahi_server_config_init(&config);
288 config.host_name = avahi_strdup(mesh->name);
289 config.publish_workstation = 0;
291 /* Allocate a new server */
293 mesh->avahi_server = avahi_server_new(avahi_simple_poll_get(mesh->avahi_poll), &config, discovery_server_callback, mesh, &error);
295 /* Free the configuration data */
296 avahi_server_config_free(&config);
298 /* Check wether creating the server object succeeded */
299 if (!mesh->avahi_server) {
300 fprintf(stderr, "Failed to create discovery server: %s\n", avahi_strerror(error));
304 // Create the service browser
305 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))) {
306 fprintf(stderr, "Failed to create discovery service browser: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
310 // Start the discovery thread
311 if(pthread_create(&mesh->discovery_thread, NULL, discovery_loop, mesh) != 0) {
312 fprintf(stderr, "Could not start discovery thread: %s\n", strerror(errno));
313 memset(&mesh->discovery_thread, 0, sizeof mesh->discovery_thread);
317 mesh->discovery_threadstarted = true;
322 if (mesh->avahi_browser)
323 avahi_s_service_browser_free(mesh->avahi_browser);
325 if (mesh->avahi_server)
326 avahi_server_free(mesh->avahi_server);
328 if (mesh->avahi_poll)
329 avahi_simple_poll_free(mesh->avahi_poll);
334 void discovery_stop(meshlink_handle_t *mesh)
337 avahi_simple_poll_quit(mesh->avahi_poll);
339 // Wait for the discovery thread to finish
341 pthread_join(mesh->discovery_thread, NULL);
343 // Clean up resources
344 if (mesh->avahi_browser)
345 avahi_s_service_browser_free(mesh->avahi_browser);
347 if (mesh->avahi_server)
348 avahi_server_free(mesh->avahi_server);
350 if (mesh->avahi_poll)
351 avahi_simple_poll_free(mesh->avahi_poll);