]> git.meshlink.io Git - meshlink/blob - src/discovery.c
Merge branch 'mesh_topology_output' into roles
[meshlink] / src / discovery.c
1
2 #include "meshlink_internal.h"
3 #include "discovery.h"
4 #include "sockaddr.h"
5 #include "logger.h"
6
7 #include <pthread.h>
8
9 #include <avahi-core/core.h>
10 #include <avahi-core/lookup.h>
11 #include <avahi-core/publish.h>
12 #include <avahi-core/log.h>
13 #include <avahi-common/simple-watch.h>
14 #include <avahi-common/malloc.h>
15 #include <avahi-common/alternative.h>
16 #include <avahi-common/error.h>
17
18 #include <netinet/in.h>
19
20 #include <uuid/uuid.h>
21
22 #define MESHLINK_MDNS_SERVICE_TYPE "_%s._tcp"
23 #define MESHLINK_MDNS_NAME_KEY "name"
24 #define MESHLINK_MDNS_FINGERPRINT_KEY "fingerprint"
25
26 static void discovery_entry_group_callback(AvahiServer *server, AvahiSEntryGroup *group, AvahiEntryGroupState state, void *userdata)
27 {
28     meshlink_handle_t *mesh = userdata;
29
30     // asserts
31     assert(mesh != NULL);
32     assert(mesh->avahi_server != NULL);
33     assert(mesh->avahi_poll != NULL);
34
35     /* Called whenever the entry group state changes */
36     switch(state)
37     {
38         case AVAHI_ENTRY_GROUP_ESTABLISHED:
39             /* The entry group has been established successfully */
40             logger(mesh, MESHLINK_DEBUG, "Avahi Service successfully established.\n");
41             break;
42
43         case AVAHI_ENTRY_GROUP_COLLISION:
44             logger(mesh, MESHLINK_WARNING, "Avahi Service collision.\n");
45             // @TODO can we just set a new name and retry?
46             break;
47
48         case AVAHI_ENTRY_GROUP_FAILURE :
49             /* Some kind of failure happened while we were registering our services */
50             logger(mesh, MESHLINK_ERROR, "Avahi Entry group failure: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
51             avahi_simple_poll_quit(mesh->avahi_poll);
52             break;
53
54         case AVAHI_ENTRY_GROUP_UNCOMMITED:
55         case AVAHI_ENTRY_GROUP_REGISTERING:
56             ;
57     }
58 }
59
60
61 static void discovery_create_services(meshlink_handle_t *mesh)
62 {
63     char *txt_name = NULL;
64
65     // asserts
66     assert(mesh != NULL);
67     assert(mesh->name != NULL);
68     assert(mesh->myport != NULL);
69     assert(mesh->avahi_server != NULL);
70     assert(mesh->avahi_poll != NULL);
71     assert(mesh->avahi_servicetype != NULL);
72     assert(mesh->self != NULL);
73
74     logger(mesh, MESHLINK_DEBUG, "Adding service\n");
75
76     /* Ifthis is the first time we're called, let's create a new entry group */
77     if(!mesh->avahi_group)
78     {
79         if(!(mesh->avahi_group = avahi_s_entry_group_new(mesh->avahi_server, discovery_entry_group_callback, mesh)))
80         {
81             logger(mesh, MESHLINK_ERROR, "avahi_entry_group_new() failed: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
82             goto fail;
83         }
84     }
85
86     /* Create txt records */
87     size_t txt_name_len = sizeof(MESHLINK_MDNS_NAME_KEY) + 1 + strlen(mesh->name) + 1;
88     txt_name = malloc(txt_name_len);
89
90     if(txt_name == NULL)
91     {
92         logger(mesh, MESHLINK_ERROR, "Could not allocate memory for TXT record\n");
93         goto fail;
94     }
95
96     snprintf(txt_name, txt_name_len, "%s=%s", MESHLINK_MDNS_NAME_KEY, mesh->name);
97
98     char txt_fingerprint[sizeof(MESHLINK_MDNS_FINGERPRINT_KEY) + 1 + MESHLINK_FINGERPRINTLEN + 1];
99     snprintf(txt_fingerprint, sizeof(txt_fingerprint), "%s=%s", MESHLINK_MDNS_FINGERPRINT_KEY, meshlink_get_fingerprint(mesh, (meshlink_node_t *)mesh->self));
100
101     /* Add the service */
102     int ret = 0;
103     if((ret = avahi_server_add_service(mesh->avahi_server, mesh->avahi_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, meshlink_get_fingerprint(mesh, (meshlink_node_t *)mesh->self), mesh->avahi_servicetype, NULL, NULL, atoi(mesh->myport), txt_name, txt_fingerprint, NULL)) < 0)
104     {
105         logger(mesh, MESHLINK_ERROR, "Failed to add service: %s\n", avahi_strerror(ret));
106         goto fail;
107     }
108
109     /* Tell the server to register the service */
110     if((ret = avahi_s_entry_group_commit(mesh->avahi_group)) < 0)
111     {
112         logger(mesh, MESHLINK_ERROR, "Failed to commit entry_group: %s\n", avahi_strerror(ret));
113         goto fail;
114     }
115
116     goto done;
117
118 fail:
119     avahi_simple_poll_quit(mesh->avahi_poll);
120
121 done:
122     if(txt_name)
123         { free(txt_name); }
124 }
125
126 static void discovery_server_callback(AvahiServer *server, AvahiServerState state, void * userdata)
127 {
128         meshlink_handle_t *mesh = userdata;
129
130     // asserts
131     assert(mesh != NULL);
132
133     switch(state)
134     {
135         case AVAHI_SERVER_RUNNING:
136             {
137                 /* The serve has startup successfully and registered its host
138                  * name on the network, so it's time to create our services */
139                 if(!mesh->avahi_group)
140                 {
141                     discovery_create_services(mesh);
142                 }
143             }
144             break;
145
146         case AVAHI_SERVER_COLLISION:
147             {
148                 // asserts
149                 assert(mesh->avahi_server != NULL);
150                 assert(mesh->avahi_poll != NULL);
151
152                 /* A host name collision happened. Let's pick a new name for the server */
153                 uuid_t hostname;
154                 uuid_generate(hostname);
155
156                 char hostnamestr[36+1];
157                 uuid_unparse_lower(hostname, hostnamestr);
158
159                 logger(mesh, MESHLINK_WARNING, "Avahi host name collision, retrying with '%s'\n", hostnamestr);
160                 int result = avahi_server_set_host_name(mesh->avahi_server, hostnamestr);
161
162                 if(result < 0)
163                 {
164                     logger(mesh, MESHLINK_ERROR, "Avahi failed to set new host name: %s\n", avahi_strerror(result));
165                     avahi_simple_poll_quit(mesh->avahi_poll);
166                 }
167             }
168             break;
169
170         case AVAHI_SERVER_REGISTERING:
171             {
172                 /* Let's drop our registered services. When the server is back
173                  * in AVAHI_SERVER_RUNNING state we will register them
174                  * again with the new host name. */
175                 if(mesh->avahi_group)
176                 {
177                     avahi_s_entry_group_reset(mesh->avahi_group);
178                     mesh->avahi_group = NULL;
179                 }
180             }
181             break;
182
183         case AVAHI_SERVER_FAILURE:
184             {
185                 // asserts
186                 assert(mesh->avahi_server != NULL);
187                 assert(mesh->avahi_poll != NULL);
188
189                 /* Terminate on failure */
190                 logger(mesh, MESHLINK_ERROR, "Avahi server failure: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
191                 avahi_simple_poll_quit(mesh->avahi_poll);
192             }
193             break;
194
195         case AVAHI_SERVER_INVALID:
196             break;
197     }
198 }
199
200 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)
201 {
202     meshlink_handle_t *mesh = userdata;
203
204     // asserts
205     assert(resolver != NULL);
206     assert(mesh != NULL);
207     assert(mesh->avahi_server != NULL);
208
209     /* Called whenever a service has been resolved successfully or timed out */
210     switch(event)
211     {
212         case AVAHI_RESOLVER_FAILURE:
213             {
214                 // asserts
215                 assert(name != NULL);
216                 assert(type != NULL);
217                 assert(domain != NULL);
218
219                 logger(mesh, MESHLINK_WARNING, "(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)));
220             }
221             break;
222
223         case AVAHI_RESOLVER_FOUND:
224             {
225                 // asserts
226                 assert(name != NULL);
227                 assert(type != NULL);
228                 assert(domain != NULL);
229                 assert(host_name != NULL);
230                 assert(address != NULL);
231                 assert(txt != NULL);
232         
233                 char straddr[AVAHI_ADDRESS_STR_MAX], *strtxt;
234
235                 logger(mesh, MESHLINK_DEBUG, "(Resolver) Service '%s' of type '%s' in domain '%s':\n", name, type, domain);
236
237                 avahi_address_snprint(straddr, sizeof(straddr), address);
238                 strtxt = avahi_string_list_to_string(txt);
239                 logger(mesh, MESHLINK_DEBUG,
240                         "\t%s:%u (%s)\n"
241                         "\tTXT=%s\n"
242                         "\tcookie is %u\n"
243                         "\tis_local: %i\n"
244                         "\twide_area: %i\n"
245                         "\tmulticast: %i\n"
246                         "\tcached: %i\n",
247                         host_name, port, straddr,
248                         strtxt,
249                         avahi_string_list_get_service_cookie(txt),
250                         !!(flags & AVAHI_LOOKUP_RESULT_LOCAL),
251                         !!(flags & AVAHI_LOOKUP_RESULT_WIDE_AREA),
252                         !!(flags & AVAHI_LOOKUP_RESULT_MULTICAST),
253                         !!(flags & AVAHI_LOOKUP_RESULT_CACHED));
254                 avahi_free(strtxt);
255
256                 // retrieve fingerprint
257                 AvahiStringList *node_name_li = avahi_string_list_find(txt, MESHLINK_MDNS_NAME_KEY);
258                 AvahiStringList *node_fp_li = avahi_string_list_find(txt, MESHLINK_MDNS_FINGERPRINT_KEY);
259
260                 if(node_name_li != NULL && node_fp_li != NULL)
261                 {
262                     char *node_name = (char*)avahi_string_list_get_text(node_name_li) + strlen(MESHLINK_MDNS_NAME_KEY);
263                     char *node_fp = (char*)avahi_string_list_get_text(node_fp_li) + strlen(MESHLINK_MDNS_FINGERPRINT_KEY);
264
265                     if(node_name[0] == '=' && node_fp[0] == '=')
266                     {
267                         node_name += 1;
268                         node_fp += 1;
269
270                         meshlink_node_t *node = meshlink_get_node(mesh, node_name);
271
272                         if(node != NULL)
273                         {
274                             logger(mesh, MESHLINK_INFO, "Node %s is part of the mesh network.\n", node->name);
275
276                             sockaddr_t naddress;
277                             memset(&naddress, 0, sizeof(naddress));
278
279                             switch(address->proto)
280                             {
281                                 case AVAHI_PROTO_INET:
282                                     {
283                                         naddress.in.sin_family = AF_INET;
284                                         naddress.in.sin_port = htons(port);
285                                         naddress.in.sin_addr.s_addr = address->data.ipv4.address;
286                                     }
287                                     break;
288
289                                 case AVAHI_PROTO_INET6:
290                                     {
291                                         naddress.in6.sin6_family = AF_INET6;
292                                         naddress.in6.sin6_port = htons(port);
293                                         memcpy(naddress.in6.sin6_addr.s6_addr, address->data.ipv6.address, sizeof(naddress.in6.sin6_addr.s6_addr));
294                                     }
295                                     break;
296
297                                 default:
298                                     naddress.unknown.family = AF_UNKNOWN;
299                                     break;
300                             }
301
302                             if(naddress.unknown.family != AF_UNKNOWN)
303                             {
304                                 meshlink_hint_address(mesh, (meshlink_node_t *)node, (struct sockaddr*)&naddress);
305                             }
306                             else
307                             {
308                                 logger(mesh, MESHLINK_WARNING, "Could not resolve node %s to a known address family type.\n", node->name);
309                             }
310                         }
311                         else
312                         {
313                             logger(mesh, MESHLINK_WARNING, "Node %s is not part of the mesh network.\n", node_name);
314                         }
315                     }
316                     else
317                     {
318                         logger(mesh, MESHLINK_WARNING, "TXT records invalid.\n");
319                     }
320                 }
321                 else
322                 {
323                     logger(mesh, MESHLINK_WARNING, "TXT records missing.\n");
324                 }
325             }
326             break;
327     }
328
329     avahi_s_service_resolver_free(resolver);
330 }
331
332 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)
333 {
334         meshlink_handle_t *mesh = userdata;
335
336     // asserts
337     assert(mesh != NULL);
338     assert(mesh->avahi_server != NULL);
339     assert(mesh->avahi_poll != NULL);
340
341     /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
342     switch (event)
343     {
344         case AVAHI_BROWSER_FAILURE:
345             {
346                 logger(mesh, MESHLINK_ERROR, "(Browser) %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
347                 avahi_simple_poll_quit(mesh->avahi_poll);
348             }
349             break;
350
351         case AVAHI_BROWSER_NEW:
352             {
353                 // asserts
354                 assert(name != NULL);
355                 assert(type != NULL);
356                 assert(domain != NULL);
357
358                 logger(mesh, MESHLINK_DEBUG, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
359                 /* We ignore the returned resolver object. In the callback
360                    function we free it. Ifthe server is terminated before
361                    the callback function is called the server will free
362                    the resolver for us. */
363                 if(!(avahi_s_service_resolver_new(mesh->avahi_server, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, discovery_resolve_callback, mesh)))
364                 {
365                     logger(mesh, MESHLINK_DEBUG, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_server_errno(mesh->avahi_server)));
366                 }
367             }
368             break;
369
370         case AVAHI_BROWSER_REMOVE:
371             {
372                 // asserts
373                 assert(name != NULL);
374                 assert(type != NULL);
375                 assert(domain != NULL);
376
377                 logger(mesh, MESHLINK_DEBUG, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
378             }
379             break;
380
381         case AVAHI_BROWSER_ALL_FOR_NOW:
382         case AVAHI_BROWSER_CACHE_EXHAUSTED:
383             {
384                 logger(mesh, MESHLINK_DEBUG, "(Browser) %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
385             }
386             break;
387     }
388 }
389
390 static void *discovery_loop(void *userdata)
391 {
392         meshlink_handle_t *mesh = userdata;
393
394     // asserts
395     assert(mesh != NULL);
396     assert(mesh->avahi_poll != NULL);
397
398     avahi_simple_poll_loop(mesh->avahi_poll);
399         
400     return NULL;
401 }
402
403 static void discovery_log_cb(AvahiLogLevel level, const char *txt)
404 {
405     meshlink_log_level_t mlevel = MESHLINK_CRITICAL;
406
407     switch(level)
408     {
409     case AVAHI_LOG_ERROR:
410         mlevel = MESHLINK_ERROR;
411         break;
412
413     case AVAHI_LOG_WARN:
414         mlevel = MESHLINK_WARNING;
415         break;
416
417     case AVAHI_LOG_NOTICE:
418     case AVAHI_LOG_INFO:
419         mlevel = MESHLINK_INFO;
420         break;
421
422     case AVAHI_LOG_DEBUG:
423         mlevel = MESHLINK_DEBUG;
424         break;
425     }
426
427     logger(NULL, mlevel, "%s\n", txt);
428 }
429
430 bool discovery_start(meshlink_handle_t *mesh)
431 {
432     logger(mesh, MESHLINK_DEBUG, "discovery_start called\n");
433
434     // asserts
435     assert(mesh != NULL);
436     assert(mesh->avahi_poll == NULL);
437     assert(mesh->avahi_server == NULL);
438     assert(mesh->avahi_browser == NULL);
439     assert(mesh->discovery_threadstarted == false);
440     assert(mesh->avahi_servicetype == NULL);
441
442     // handle avahi logs
443     avahi_set_log_function(discovery_log_cb);
444     
445     // create service type string
446     size_t servicetype_strlen = sizeof(MESHLINK_MDNS_SERVICE_TYPE) + strlen(mesh->appname) + 1;
447     mesh->avahi_servicetype = malloc(servicetype_strlen);
448
449     if(mesh->avahi_servicetype == NULL)
450     {
451         logger(mesh, MESHLINK_ERROR, "Failed to allocate memory for service type string.\n");
452         goto fail;
453     }
454
455     snprintf(mesh->avahi_servicetype, servicetype_strlen, MESHLINK_MDNS_SERVICE_TYPE, mesh->appname);
456
457     // Allocate discovery loop object
458     if(!(mesh->avahi_poll = avahi_simple_poll_new()))
459     {
460         logger(mesh, MESHLINK_ERROR, "Failed to create discovery poll object.\n");
461                 goto fail;
462     }
463
464     // generate some unique host name (we actually do not care about it)
465     uuid_t hostname;
466     uuid_generate(hostname);
467
468     char hostnamestr[36+1];
469     uuid_unparse_lower(hostname, hostnamestr);
470
471     // Let's set the host name for this server.
472     AvahiServerConfig config;
473     avahi_server_config_init(&config);
474     config.host_name = avahi_strdup(hostnamestr);
475     config.publish_workstation = 0;
476     config.disallow_other_stacks = 0;
477     config.publish_hinfo = 0;
478     config.publish_addresses = 1;
479     config.publish_no_reverse = 1;
480
481     /* Allocate a new server */
482     int error;
483     mesh->avahi_server = avahi_server_new(avahi_simple_poll_get(mesh->avahi_poll), &config, discovery_server_callback, mesh, &error);
484
485     /* Free the configuration data */
486     avahi_server_config_free(&config);
487
488     /* Check wether creating the server object succeeded */
489     if(!mesh->avahi_server)
490     {
491         logger(mesh, MESHLINK_ERROR, "Failed to create discovery server: %s\n", avahi_strerror(error));
492         goto fail;
493     }
494
495     // Create the service browser
496     if(!(mesh->avahi_browser = avahi_s_service_browser_new(mesh->avahi_server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, mesh->avahi_servicetype, NULL, 0, discovery_browse_callback, mesh)))
497     {
498         logger(mesh, MESHLINK_ERROR, "Failed to create discovery service browser: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
499         goto fail;
500     }
501
502         // Start the discovery thread
503         if(pthread_create(&mesh->discovery_thread, NULL, discovery_loop, mesh) != 0)
504     {
505                 logger(mesh, MESHLINK_ERROR, "Could not start discovery thread: %s\n", strerror(errno));
506                 memset(&mesh->discovery_thread, 0, sizeof mesh->discovery_thread);
507                 goto fail;
508         }
509
510         mesh->discovery_threadstarted = true;
511
512         return true;
513
514 fail:
515     if(mesh->avahi_browser != NULL)
516     {
517         avahi_s_service_browser_free(mesh->avahi_browser);
518         mesh->avahi_browser = NULL;
519     }
520
521     if(mesh->avahi_server != NULL)
522     {
523         avahi_server_free(mesh->avahi_server);
524         mesh->avahi_server = NULL;
525     }
526
527     if(mesh->avahi_poll != NULL)
528     {
529         avahi_simple_poll_free(mesh->avahi_poll);
530         mesh->avahi_poll = NULL;
531     }
532
533     if(mesh->avahi_servicetype != NULL)
534     {
535         free(mesh->avahi_servicetype);
536         mesh->avahi_servicetype = NULL;
537     }
538
539     return false;
540 }
541
542 void discovery_stop(meshlink_handle_t *mesh)
543 {
544     logger(mesh, MESHLINK_DEBUG, "discovery_stop called\n");
545
546     // asserts
547     assert(mesh != NULL);
548
549         // Shut down
550     if(mesh->avahi_poll)
551     {
552         avahi_simple_poll_quit(mesh->avahi_poll);
553     }
554
555         // Wait for the discovery thread to finish
556     if(mesh->discovery_threadstarted == true)
557     {
558         pthread_join(mesh->discovery_thread, NULL);
559         mesh->discovery_threadstarted = false;
560     }
561
562         // Clean up resources
563     if(mesh->avahi_browser != NULL)
564     {
565         avahi_s_service_browser_free(mesh->avahi_browser);
566         mesh->avahi_browser = NULL;
567     }
568
569     if(mesh->avahi_group)
570     {
571         avahi_s_entry_group_reset(mesh->avahi_group);
572         avahi_s_entry_group_free(mesh->avahi_group);
573         mesh->avahi_group = NULL;
574     }
575
576     if(mesh->avahi_server != NULL)
577     {
578         avahi_server_free(mesh->avahi_server);
579         mesh->avahi_server = NULL;
580     }
581
582     if(mesh->avahi_poll != NULL)
583     {
584         avahi_simple_poll_free(mesh->avahi_poll);
585         mesh->avahi_poll = NULL;
586     }
587
588     if(mesh->avahi_servicetype != NULL)
589     {
590         free(mesh->avahi_servicetype);
591         mesh->avahi_servicetype = NULL;
592     }
593 }