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 meshlink_hint_address(mesh, node->name, &naddr);
114 fprintf(stderr, "Node %s is not part of the mesh network - ignoring ip address.\n", node ? node->name : "n/a");
119 avahi_s_service_resolver_free(resolver);
122 static void discovery_entry_group_callback(AvahiServer *server, AvahiSEntryGroup *group, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata)
124 meshlink_handle_t *mesh = userdata;
126 /* Called whenever the entry group state changes */
129 case AVAHI_ENTRY_GROUP_ESTABLISHED:
130 /* The entry group has been established successfully */
131 fprintf(stderr, "Service '%s' successfully established.\n", /*MESHLINK_MDNS_SERVICE_NAME*/ mesh->name);
134 case AVAHI_ENTRY_GROUP_COLLISION:
135 fprintf(stderr, "Service name collision '%s'\n", /*MESHLINK_MDNS_SERVICE_NAME*/ mesh->name);
138 case AVAHI_ENTRY_GROUP_FAILURE :
139 fprintf(stderr, "Entry group failure: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
141 /* Some kind of failure happened while we were registering our services */
142 avahi_simple_poll_quit(mesh->avahi_poll);
145 case AVAHI_ENTRY_GROUP_UNCOMMITED:
146 case AVAHI_ENTRY_GROUP_REGISTERING:
152 static void discovery_create_services(meshlink_handle_t *mesh)
154 fprintf(stderr, "Adding service '%s'\n", /*MESHLINK_MDNS_SERVICE_NAME*/ mesh->name);
156 /* If this is the first time we're called, let's create a new entry group */
157 if (!mesh->avahi_group)
158 if (!(mesh->avahi_group = avahi_s_entry_group_new(mesh->avahi_server, discovery_entry_group_callback, mesh))) {
159 fprintf(stderr, "avahi_entry_group_new() failed: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
163 /* Create some random TXT data */
164 char fingerprint[1024] = "";
165 snprintf(fingerprint, sizeof(fingerprint), "%s=%s", MESHLINK_MDNS_FINGERPRINT_KEY, meshlink_get_fingerprint(mesh, meshlink_get_node(mesh, mesh->name)));
167 /* Add the service for IPP */
169 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) {
170 fprintf(stderr, "Failed to add _ipp._tcp service: %s\n", avahi_strerror(ret));
174 /* Tell the server to register the service */
175 if ((ret = avahi_s_entry_group_commit(mesh->avahi_group)) < 0) {
176 fprintf(stderr, "Failed to commit entry_group: %s\n", avahi_strerror(ret));
183 avahi_simple_poll_quit(mesh->avahi_poll);
186 static void discovery_server_callback(AvahiServer *server, AvahiServerState state, AVAHI_GCC_UNUSED void * userdata)
188 meshlink_handle_t *mesh = userdata;
192 case AVAHI_SERVER_RUNNING:
193 /* The serve has startup successfully and registered its host
194 * name on the network, so it's time to create our services */
195 if (!mesh->avahi_group)
196 discovery_create_services(mesh);
199 case AVAHI_SERVER_COLLISION:
200 /* A host name collision happened. Let's do nothing */
203 case AVAHI_SERVER_REGISTERING:
204 /* Let's drop our registered services. When the server is back
205 * in AVAHI_SERVER_RUNNING state we will register them
206 * again with the new host name. */
207 //if (mesh->avahi_group)
208 // avahi_s_entry_group_reset(mesh->avahi_group);
211 case AVAHI_SERVER_FAILURE:
212 /* Terminate on failure */
213 fprintf(stderr, "Server failure: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
214 avahi_simple_poll_quit(mesh->avahi_poll);
217 case AVAHI_SERVER_INVALID:
222 static void discovery_browse_callback(
223 AvahiSServiceBrowser *browser,
224 AvahiIfIndex interface,
225 AvahiProtocol protocol,
226 AvahiBrowserEvent event,
230 AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
233 meshlink_handle_t *mesh = userdata;
235 /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
239 case AVAHI_BROWSER_FAILURE:
240 fprintf(stderr, "(Browser) %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
241 avahi_simple_poll_quit(mesh->avahi_poll);
244 case AVAHI_BROWSER_NEW:
245 fprintf(stderr, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
246 /* We ignore the returned resolver object. In the callback
247 function we free it. If the server is terminated before
248 the callback function is called the server will free
249 the resolver for us. */
250 if (!(avahi_s_service_resolver_new(mesh->avahi_server, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, discovery_resolve_callback, mesh)))
251 fprintf(stderr, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_server_errno(mesh->avahi_server)));
254 case AVAHI_BROWSER_REMOVE:
255 fprintf(stderr, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
258 case AVAHI_BROWSER_ALL_FOR_NOW:
259 case AVAHI_BROWSER_CACHE_EXHAUSTED:
260 fprintf(stderr, "(Browser) %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
265 static void *discovery_loop(void *arg)
267 meshlink_handle_t *mesh = arg;
269 avahi_simple_poll_loop(mesh->avahi_poll);
274 bool discovery_start(meshlink_handle_t *mesh)
276 // Allocate discovery loop object
277 if (!(mesh->avahi_poll = avahi_simple_poll_new())) {
278 fprintf(stderr, "Failed to create discovery poll object.\n");
282 // Let's set the host name for this server.
283 AvahiServerConfig config;
284 avahi_server_config_init(&config);
285 config.host_name = avahi_strdup(mesh->name);
286 config.publish_workstation = 0;
288 /* Allocate a new server */
290 mesh->avahi_server = avahi_server_new(avahi_simple_poll_get(mesh->avahi_poll), &config, discovery_server_callback, mesh, &error);
292 /* Free the configuration data */
293 avahi_server_config_free(&config);
295 /* Check wether creating the server object succeeded */
296 if (!mesh->avahi_server) {
297 fprintf(stderr, "Failed to create discovery server: %s\n", avahi_strerror(error));
301 // Create the service browser
302 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))) {
303 fprintf(stderr, "Failed to create discovery service browser: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
307 // Start the discovery thread
308 if(pthread_create(&mesh->discovery_thread, NULL, discovery_loop, mesh) != 0) {
309 fprintf(stderr, "Could not start discovery thread: %s\n", strerror(errno));
310 memset(&mesh->discovery_thread, 0, sizeof mesh->discovery_thread);
314 mesh->discovery_threadstarted = true;
319 if (mesh->avahi_browser)
320 avahi_s_service_browser_free(mesh->avahi_browser);
322 if (mesh->avahi_server)
323 avahi_server_free(mesh->avahi_server);
325 if (mesh->avahi_poll)
326 avahi_simple_poll_free(mesh->avahi_poll);
331 void discovery_stop(meshlink_handle_t *mesh)
334 avahi_simple_poll_quit(mesh->avahi_poll);
336 // Wait for the discovery thread to finish
338 pthread_join(mesh->discovery_thread, NULL);
340 // Clean up resources
341 if (mesh->avahi_browser)
342 avahi_s_service_browser_free(mesh->avahi_browser);
344 if (mesh->avahi_server)
345 avahi_server_free(mesh->avahi_server);
347 if (mesh->avahi_poll)
348 avahi_simple_poll_free(mesh->avahi_poll);