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"
22 // @TODO: aquire mutex in case we call meshlink_* methods?
25 static void discovery_resolve_callback(
26 AvahiSServiceResolver *resolver,
27 AVAHI_GCC_UNUSED AvahiIfIndex interface,
28 AVAHI_GCC_UNUSED AvahiProtocol protocol,
29 AvahiResolverEvent event,
33 const char *host_name,
34 const AvahiAddress *address,
37 AvahiLookupResultFlags flags,
38 AVAHI_GCC_UNUSED void* userdata)
41 meshlink_handle_t *mesh = userdata;
43 /* Called whenever a service has been resolved successfully or timed out */
47 case AVAHI_RESOLVER_FAILURE:
48 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)));
51 case AVAHI_RESOLVER_FOUND:
53 char straddr[AVAHI_ADDRESS_STR_MAX], *strtxt;
55 fprintf(stderr, "(Resolver) Service '%s' of type '%s' in domain '%s':\n", name, type, domain);
57 avahi_address_snprint(straddr, sizeof(straddr), address);
58 strtxt = avahi_string_list_to_string(txt);
67 host_name, port, straddr,
69 avahi_string_list_get_service_cookie(txt),
70 !!(flags & AVAHI_LOOKUP_RESULT_LOCAL),
71 !!(flags & AVAHI_LOOKUP_RESULT_WIDE_AREA),
72 !!(flags & AVAHI_LOOKUP_RESULT_MULTICAST),
73 !!(flags & AVAHI_LOOKUP_RESULT_CACHED));
76 // retrieve fingerprint
77 AvahiStringList *fgli = avahi_string_list_find(txt, MESHLINK_MDNS_FINGERPRINT_KEY);
78 meshlink_node_t *node = meshlink_get_node(mesh, name);
80 fprintf(stderr, "%p, %p, %s, %s\n", fgli, node, avahi_string_list_get_text(fgli), meshlink_get_fingerprint(mesh, node));
82 if( node && fgli && strcmp(avahi_string_list_get_text(fgli)+strlen(MESHLINK_MDNS_FINGERPRINT_KEY)+1, meshlink_get_fingerprint(mesh, node)) == 0 )
84 fprintf(stderr, "Node %s is part of the mesh network - updating ip address.\n", node->name);
86 struct sockaddr_storage naddr;
87 memset(&naddr, 0, sizeof(naddr));
89 switch(address->proto)
91 case AVAHI_PROTO_INET:
93 struct sockaddr_in* naddr_in = (struct sockaddr_in*)&naddr;
94 naddr_in->sin_family = AF_INET;
95 naddr_in->sin_port = port;
96 naddr_in->sin_addr.s_addr = address->data.ipv4.address;
100 case AVAHI_PROTO_INET6:
102 struct sockaddr_in6* naddr_in = (struct sockaddr_in6*)&naddr;
103 naddr_in->sin6_family = AF_INET6;
104 naddr_in->sin6_port = port;
105 memcpy(naddr_in->sin6_addr.s6_addr, address->data.ipv6.address, sizeof(naddr_in->sin6_addr.s6_addr));
110 naddr.ss_family = AF_UNKNOWN;
113 if(naddr.ss_family == AF_INET || naddr.ss_family == AF_INET6)
115 meshlink_hint_address(mesh, node->name, (struct sockaddr*)&naddr);
120 fprintf(stderr, "Node %s is not part of the mesh network - ignoring ip address.\n", node ? node->name : "n/a");
125 avahi_s_service_resolver_free(resolver);
128 static void discovery_entry_group_callback(AvahiServer *server, AvahiSEntryGroup *group, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata)
130 meshlink_handle_t *mesh = userdata;
132 /* Called whenever the entry group state changes */
135 case AVAHI_ENTRY_GROUP_ESTABLISHED:
136 /* The entry group has been established successfully */
137 fprintf(stderr, "Service '%s' successfully established.\n", /*MESHLINK_MDNS_SERVICE_NAME*/ mesh->name);
140 case AVAHI_ENTRY_GROUP_COLLISION:
141 fprintf(stderr, "Service name collision '%s'\n", /*MESHLINK_MDNS_SERVICE_NAME*/ mesh->name);
144 case AVAHI_ENTRY_GROUP_FAILURE :
145 fprintf(stderr, "Entry group failure: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
147 /* Some kind of failure happened while we were registering our services */
148 avahi_simple_poll_quit(mesh->avahi_poll);
151 case AVAHI_ENTRY_GROUP_UNCOMMITED:
152 case AVAHI_ENTRY_GROUP_REGISTERING:
158 static void discovery_create_services(meshlink_handle_t *mesh)
160 fprintf(stderr, "Adding service '%s'\n", /*MESHLINK_MDNS_SERVICE_NAME*/ mesh->name);
162 /* If this is the first time we're called, let's create a new entry group */
163 if (!mesh->avahi_group)
164 if (!(mesh->avahi_group = avahi_s_entry_group_new(mesh->avahi_server, discovery_entry_group_callback, mesh))) {
165 fprintf(stderr, "avahi_entry_group_new() failed: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
169 /* Create some random TXT data */
170 char fingerprint[1024] = "";
171 snprintf(fingerprint, sizeof(fingerprint), "%s=%s", MESHLINK_MDNS_FINGERPRINT_KEY, meshlink_get_fingerprint(mesh, meshlink_get_node(mesh, mesh->name)));
173 /* Add the service for IPP */
175 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) {
176 fprintf(stderr, "Failed to add _ipp._tcp service: %s\n", avahi_strerror(ret));
180 /* Tell the server to register the service */
181 if ((ret = avahi_s_entry_group_commit(mesh->avahi_group)) < 0) {
182 fprintf(stderr, "Failed to commit entry_group: %s\n", avahi_strerror(ret));
189 avahi_simple_poll_quit(mesh->avahi_poll);
192 static void discovery_server_callback(AvahiServer *server, AvahiServerState state, AVAHI_GCC_UNUSED void * userdata)
194 meshlink_handle_t *mesh = userdata;
198 case AVAHI_SERVER_RUNNING:
199 /* The serve has startup successfully and registered its host
200 * name on the network, so it's time to create our services */
201 if (!mesh->avahi_group)
202 discovery_create_services(mesh);
205 case AVAHI_SERVER_COLLISION:
206 /* A host name collision happened. Let's do nothing */
209 case AVAHI_SERVER_REGISTERING:
210 /* Let's drop our registered services. When the server is back
211 * in AVAHI_SERVER_RUNNING state we will register them
212 * again with the new host name. */
213 //if (mesh->avahi_group)
214 // avahi_s_entry_group_reset(mesh->avahi_group);
217 case AVAHI_SERVER_FAILURE:
218 /* Terminate on failure */
219 fprintf(stderr, "Server failure: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
220 avahi_simple_poll_quit(mesh->avahi_poll);
223 case AVAHI_SERVER_INVALID:
228 static void discovery_browse_callback(
229 AvahiSServiceBrowser *browser,
230 AvahiIfIndex interface,
231 AvahiProtocol protocol,
232 AvahiBrowserEvent event,
236 AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
239 meshlink_handle_t *mesh = userdata;
241 /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
245 case AVAHI_BROWSER_FAILURE:
246 fprintf(stderr, "(Browser) %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
247 avahi_simple_poll_quit(mesh->avahi_poll);
250 case AVAHI_BROWSER_NEW:
251 fprintf(stderr, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
252 /* We ignore the returned resolver object. In the callback
253 function we free it. If the server is terminated before
254 the callback function is called the server will free
255 the resolver for us. */
256 if (!(avahi_s_service_resolver_new(mesh->avahi_server, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, discovery_resolve_callback, mesh)))
257 fprintf(stderr, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_server_errno(mesh->avahi_server)));
260 case AVAHI_BROWSER_REMOVE:
261 fprintf(stderr, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
264 case AVAHI_BROWSER_ALL_FOR_NOW:
265 case AVAHI_BROWSER_CACHE_EXHAUSTED:
266 fprintf(stderr, "(Browser) %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
271 static void *discovery_loop(void *arg)
273 meshlink_handle_t *mesh = arg;
275 avahi_simple_poll_loop(mesh->avahi_poll);
280 bool discovery_start(meshlink_handle_t *mesh)
282 // Allocate discovery loop object
283 if (!(mesh->avahi_poll = avahi_simple_poll_new())) {
284 fprintf(stderr, "Failed to create discovery poll object.\n");
288 // Let's set the host name for this server.
289 AvahiServerConfig config;
290 avahi_server_config_init(&config);
291 config.host_name = avahi_strdup(mesh->name);
292 config.publish_workstation = 0;
294 /* Allocate a new server */
296 mesh->avahi_server = avahi_server_new(avahi_simple_poll_get(mesh->avahi_poll), &config, discovery_server_callback, mesh, &error);
298 /* Free the configuration data */
299 avahi_server_config_free(&config);
301 /* Check wether creating the server object succeeded */
302 if (!mesh->avahi_server) {
303 fprintf(stderr, "Failed to create discovery server: %s\n", avahi_strerror(error));
307 // Create the service browser
308 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))) {
309 fprintf(stderr, "Failed to create discovery service browser: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
313 // Start the discovery thread
314 if(pthread_create(&mesh->discovery_thread, NULL, discovery_loop, mesh) != 0) {
315 fprintf(stderr, "Could not start discovery thread: %s\n", strerror(errno));
316 memset(&mesh->discovery_thread, 0, sizeof mesh->discovery_thread);
320 mesh->discovery_threadstarted = true;
325 if (mesh->avahi_browser)
326 avahi_s_service_browser_free(mesh->avahi_browser);
328 if (mesh->avahi_server)
329 avahi_server_free(mesh->avahi_server);
331 if (mesh->avahi_poll)
332 avahi_simple_poll_free(mesh->avahi_poll);
337 void discovery_stop(meshlink_handle_t *mesh)
340 avahi_simple_poll_quit(mesh->avahi_poll);
342 // Wait for the discovery thread to finish
344 pthread_join(mesh->discovery_thread, NULL);
346 // Clean up resources
347 if (mesh->avahi_browser)
348 avahi_s_service_browser_free(mesh->avahi_browser);
350 if (mesh->avahi_server)
351 avahi_server_free(mesh->avahi_server);
353 if (mesh->avahi_poll)
354 avahi_simple_poll_free(mesh->avahi_poll);