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