]> git.meshlink.io Git - meshlink/blob - src/discovery.c
use uuid for hostname and service name, because we exchange all important information...
[meshlink] / src / discovery.c
1
2 #include "meshlink_internal.h"
3 #include "discovery.h"
4 #include "sockaddr.h"
5
6 #include <pthread.h>
7
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>
15
16 #include <netinet/in.h>
17
18 #include <uuid/uuid.h>
19
20 #define MESHLINK_MDNS_SERVICE_TYPE "_meshlink._tcp"
21 #define MESHLINK_MDNS_NAME_KEY "name"
22 #define MESHLINK_MDNS_FINGERPRINT_KEY "fingerprint"
23
24 static void discovery_entry_group_callback(AvahiServer *server, AvahiSEntryGroup *group, AvahiEntryGroupState state, void *userdata)
25 {
26     meshlink_handle_t *mesh = userdata;
27
28     // asserts
29     assert(mesh != NULL);
30     assert(mesh->avahi_server != NULL);
31     assert(mesh->avahi_poll != NULL);
32
33     /* Called whenever the entry group state changes */
34     switch(state)
35     {
36         case AVAHI_ENTRY_GROUP_ESTABLISHED:
37             /* The entry group has been established successfully */
38             fprintf(stderr, "Service successfully established.\n");
39             break;
40
41         case AVAHI_ENTRY_GROUP_COLLISION:
42             fprintf(stderr, "Service collision\n");
43             // @TODO can we just set a new name and retry?
44             break;
45
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);
50             break;
51
52         case AVAHI_ENTRY_GROUP_UNCOMMITED:
53         case AVAHI_ENTRY_GROUP_REGISTERING:
54             ;
55     }
56 }
57
58
59 static void discovery_create_services(meshlink_handle_t *mesh)
60 {
61     char *txt_name = NULL;
62
63     // asserts
64     assert(mesh != NULL);
65     assert(mesh->name != NULL);
66     assert(mesh->myport != NULL);
67     assert(mesh->avahi_server != NULL);
68     assert(mesh->avahi_poll != NULL);
69
70     fprintf(stderr, "Adding service\n");
71
72     /* Ifthis is the first time we're called, let's create a new entry group */
73     if(!mesh->avahi_group)
74     {
75         if(!(mesh->avahi_group = avahi_s_entry_group_new(mesh->avahi_server, discovery_entry_group_callback, mesh)))
76         {
77             fprintf(stderr, "avahi_entry_group_new() failed: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
78             goto fail;
79         }
80     }
81
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);
86
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));
89
90     // Generate a name for the service (actually we do not care)
91     uuid_t srvname;
92     uuid_generate(srvname);
93
94     char srvnamestr[36+1];
95     uuid_unparse_lower(srvname, srvnamestr);
96
97     /* Add the service */
98     int ret = 0;
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)
100     {
101         fprintf(stderr, "Failed to add service: %s\n", avahi_strerror(ret));
102         goto fail;
103     }
104
105     /* Tell the server to register the service */
106     if((ret = avahi_s_entry_group_commit(mesh->avahi_group)) < 0)
107     {
108         fprintf(stderr, "Failed to commit entry_group: %s\n", avahi_strerror(ret));
109         goto fail;
110     }
111
112     goto done;
113
114 fail:
115     avahi_simple_poll_quit(mesh->avahi_poll);
116
117 done:
118     if(txt_name)
119         free(txt_name);
120 }
121
122 static void discovery_server_callback(AvahiServer *server, AvahiServerState state, void * userdata)
123 {
124         meshlink_handle_t *mesh = userdata;
125
126     // asserts
127     assert(mesh != NULL);
128
129     switch(state)
130     {
131         case AVAHI_SERVER_RUNNING:
132             {
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)
136                 {
137                     discovery_create_services(mesh);
138                 }
139             }
140             break;
141
142         case AVAHI_SERVER_COLLISION:
143             {
144                 // asserts
145                 assert(mesh->avahi_server != NULL);
146                 assert(mesh->avahi_poll != NULL);
147
148                 /* A host name collision happened. Let's pick a new name for the server */
149                 uuid_t hostname;
150                 uuid_generate(hostname);
151
152                 char hostnamestr[36+1];
153                 uuid_unparse_lower(hostname, hostnamestr);
154
155                 fprintf(stderr, "Host name collision, retrying with '%s'\n", hostnamestr);
156                 int result = avahi_server_set_host_name(mesh->avahi_server, hostnamestr);
157
158                 if(result < 0)
159                 {
160                     fprintf(stderr, "Failed to set new host name: %s\n", avahi_strerror(result));
161                     avahi_simple_poll_quit(mesh->avahi_poll);
162                     return;
163                 }
164             }
165             break;
166
167         case AVAHI_SERVER_REGISTERING:
168             {
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)
173                 {
174                     avahi_s_entry_group_reset(mesh->avahi_group);
175                     mesh->avahi_group = NULL;
176                 }
177             }
178             break;
179
180         case AVAHI_SERVER_FAILURE:
181             {
182                 // asserts
183                 assert(mesh->avahi_server != NULL);
184                 assert(mesh->avahi_poll != NULL);
185
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);
189             }
190             break;
191
192         case AVAHI_SERVER_INVALID:
193             break;
194     }
195 }
196
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)
198 {
199     meshlink_handle_t *mesh = userdata;
200
201     // asserts
202     assert(resolver != NULL);
203     assert(mesh != NULL);
204     assert(mesh->avahi_server != NULL);
205
206     /* Called whenever a service has been resolved successfully or timed out */
207     switch(event)
208     {
209         case AVAHI_RESOLVER_FAILURE:
210             {
211                 // asserts
212                 assert(name != NULL);
213                 assert(type != NULL);
214                 assert(domain != NULL);
215
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)));
217             }
218             break;
219
220         case AVAHI_RESOLVER_FOUND:
221             {
222                 // asserts
223                 assert(name != NULL);
224                 assert(type != NULL);
225                 assert(domain != NULL);
226                 assert(host_name != NULL);
227                 assert(address != NULL);
228                 assert(txt != NULL);
229         
230                 char straddr[AVAHI_ADDRESS_STR_MAX], *strtxt;
231
232                 fprintf(stderr, "(Resolver) Service '%s' of type '%s' in domain '%s':\n", name, type, domain);
233
234                 avahi_address_snprint(straddr, sizeof(straddr), address);
235                 strtxt = avahi_string_list_to_string(txt);
236                 fprintf(stderr,
237                         "\t%s:%u (%s)\n"
238                         "\tTXT=%s\n"
239                         "\tcookie is %u\n"
240                         "\tis_local: %i\n"
241                         "\twide_area: %i\n"
242                         "\tmulticast: %i\n"
243                         "\tcached: %i\n",
244                         host_name, port, straddr,
245                         strtxt,
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));
251                 avahi_free(strtxt);
252
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);
256
257                 if(node_name_li != NULL && node_fp_li != NULL)
258                 {
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;
261
262                     meshlink_node_t *node = meshlink_get_node(mesh, node_name);
263
264                     if(node != NULL)
265                     {
266                         fprintf(stderr, "Node %s is part of the mesh network.\n", node->name);
267
268                         sockaddr_t naddress;
269                         memset(&naddress, 0, sizeof(naddress));
270
271                         switch(address->proto)
272                         {
273                             case AVAHI_PROTO_INET:
274                                 {
275                                     naddress.in.sin_family = AF_INET;
276                                     naddress.in.sin_port = port;
277                                     naddress.in.sin_addr.s_addr = address->data.ipv4.address;
278                                 }
279                                 break;
280
281                             case AVAHI_PROTO_INET6:
282                                 {
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));
286                                 }
287                                 break;
288
289                             default:
290                                 naddress.unknown.family = AF_UNKNOWN;
291                                 break;
292                         }
293
294                         if(naddress.unknown.family != AF_UNKNOWN)
295                         {
296                             meshlink_hint_address(mesh, (meshlink_node_t *)node, (struct sockaddr*)&naddress);
297                         }
298                         else
299                         {
300                             fprintf(stderr, "Could not resolve node %s to a known address family type.\n", node->name);
301                         }
302                     }
303                     else
304                     {
305                         fprintf(stderr, "Node %s is not part of the mesh network.\n", node_name);
306                     }
307                 }
308                 else
309                 {
310                     fprintf(stderr, "TXT records missing.\n");
311                 }
312             }
313             break;
314     }
315
316     avahi_s_service_resolver_free(resolver);
317 }
318
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)
320 {
321         meshlink_handle_t *mesh = userdata;
322
323     // asserts
324     assert(mesh != NULL);
325     assert(mesh->avahi_server != NULL);
326     assert(mesh->avahi_poll != NULL);
327
328     /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
329     switch (event)
330     {
331         case AVAHI_BROWSER_FAILURE:
332             {
333                 fprintf(stderr, "(Browser) %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
334                 avahi_simple_poll_quit(mesh->avahi_poll);
335                 return;
336             }
337
338         case AVAHI_BROWSER_NEW:
339             {
340                 // asserts
341                 assert(name != NULL);
342                 assert(type != NULL);
343                 assert(domain != NULL);
344
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)))
351                 {
352                     fprintf(stderr, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_server_errno(mesh->avahi_server)));
353                 }
354             }
355             break;
356
357         case AVAHI_BROWSER_REMOVE:
358             {
359                 // asserts
360                 assert(name != NULL);
361                 assert(type != NULL);
362                 assert(domain != NULL);
363
364                 fprintf(stderr, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
365             }
366             break;
367
368         case AVAHI_BROWSER_ALL_FOR_NOW:
369         case AVAHI_BROWSER_CACHE_EXHAUSTED:
370             {
371                 fprintf(stderr, "(Browser) %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
372             }
373             break;
374     }
375 }
376
377 static void *discovery_loop(void *userdata)
378 {
379         meshlink_handle_t *mesh = userdata;
380
381     // asserts
382     assert(mesh != NULL);
383     assert(mesh->avahi_poll != NULL);
384
385     avahi_simple_poll_loop(mesh->avahi_poll);
386         
387     return NULL;
388 }
389
390 bool discovery_start(meshlink_handle_t *mesh)
391 {
392     // asserts
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);
398
399     // Allocate discovery loop object
400     if(!(mesh->avahi_poll = avahi_simple_poll_new()))
401     {
402         fprintf(stderr, "Failed to create discovery poll object.\n");
403                 goto fail;
404     }
405
406     // generate some unique host name (we actually do not care about it)
407     uuid_t hostname;
408     uuid_generate(hostname);
409
410     char hostnamestr[36+1];
411     uuid_unparse_lower(hostname, hostnamestr);
412
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;
422
423     /* Allocate a new server */
424     int error;
425     mesh->avahi_server = avahi_server_new(avahi_simple_poll_get(mesh->avahi_poll), &config, discovery_server_callback, mesh, &error);
426
427     /* Free the configuration data */
428     avahi_server_config_free(&config);
429
430     /* Check wether creating the server object succeeded */
431     if(!mesh->avahi_server)
432     {
433         fprintf(stderr, "Failed to create discovery server: %s\n", avahi_strerror(error));
434         goto fail;
435     }
436
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)))
439     {
440         fprintf(stderr, "Failed to create discovery service browser: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
441         goto fail;
442     }
443
444         // Start the discovery thread
445         if(pthread_create(&mesh->discovery_thread, NULL, discovery_loop, mesh) != 0)
446     {
447                 fprintf(stderr, "Could not start discovery thread: %s\n", strerror(errno));
448                 memset(&mesh->discovery_thread, 0, sizeof mesh->discovery_thread);
449                 goto fail;
450         }
451
452         mesh->discovery_threadstarted = true;
453
454         return true;
455
456 fail:
457     if(mesh->avahi_browser)
458     {
459         avahi_s_service_browser_free(mesh->avahi_browser);
460         mesh->avahi_browser = NULL;
461     }
462
463     if(mesh->avahi_server)
464     {
465         avahi_server_free(mesh->avahi_server);
466         mesh->avahi_server = NULL;
467     }
468
469     if(mesh->avahi_poll)
470     {
471         avahi_simple_poll_free(mesh->avahi_poll);
472         mesh->avahi_poll = NULL;
473     }
474
475     return false;
476 }
477
478 void discovery_stop(meshlink_handle_t *mesh)
479 {
480     // asserts
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);
486
487         // Shut down 
488         avahi_simple_poll_quit(mesh->avahi_poll);
489
490         // Wait for the discovery thread to finish
491         pthread_join(mesh->discovery_thread, NULL);
492
493         // Clean up resources
494     avahi_s_service_browser_free(mesh->avahi_browser);
495     mesh->avahi_browser = NULL;
496
497     avahi_server_free(mesh->avahi_server);
498     mesh->avahi_server = NULL;
499
500     avahi_simple_poll_free(mesh->avahi_poll);
501     mesh->avahi_poll = NULL;
502 }