]> git.meshlink.io Git - meshlink/blob - src/discovery.c
Merge branch 'everbase' of chicago.everbase.net:meshlink/meshlink into everbase
[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                     return;
167                 }
168             }
169             break;
170
171         case AVAHI_SERVER_REGISTERING:
172             {
173                 /* Let's drop our registered services. When the server is back
174                  * in AVAHI_SERVER_RUNNING state we will register them
175                  * again with the new host name. */
176                 if(mesh->avahi_group)
177                 {
178                     avahi_s_entry_group_reset(mesh->avahi_group);
179                     mesh->avahi_group = NULL;
180                 }
181             }
182             break;
183
184         case AVAHI_SERVER_FAILURE:
185             {
186                 // asserts
187                 assert(mesh->avahi_server != NULL);
188                 assert(mesh->avahi_poll != NULL);
189
190                 /* Terminate on failure */
191                 logger(mesh, MESHLINK_ERROR, "Avahi server failure: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
192                 avahi_simple_poll_quit(mesh->avahi_poll);
193             }
194             break;
195
196         case AVAHI_SERVER_INVALID:
197             break;
198     }
199 }
200
201 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)
202 {
203     meshlink_handle_t *mesh = userdata;
204
205     // asserts
206     assert(resolver != NULL);
207     assert(mesh != NULL);
208     assert(mesh->avahi_server != NULL);
209
210     /* Called whenever a service has been resolved successfully or timed out */
211     switch(event)
212     {
213         case AVAHI_RESOLVER_FAILURE:
214             {
215                 // asserts
216                 assert(name != NULL);
217                 assert(type != NULL);
218                 assert(domain != NULL);
219
220                 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)));
221             }
222             break;
223
224         case AVAHI_RESOLVER_FOUND:
225             {
226                 // asserts
227                 assert(name != NULL);
228                 assert(type != NULL);
229                 assert(domain != NULL);
230                 assert(host_name != NULL);
231                 assert(address != NULL);
232                 assert(txt != NULL);
233         
234                 char straddr[AVAHI_ADDRESS_STR_MAX], *strtxt;
235
236                 logger(mesh, MESHLINK_DEBUG, "(Resolver) Service '%s' of type '%s' in domain '%s':\n", name, type, domain);
237
238                 avahi_address_snprint(straddr, sizeof(straddr), address);
239                 strtxt = avahi_string_list_to_string(txt);
240                 logger(mesh, MESHLINK_DEBUG,
241                         "\t%s:%u (%s)\n"
242                         "\tTXT=%s\n"
243                         "\tcookie is %u\n"
244                         "\tis_local: %i\n"
245                         "\twide_area: %i\n"
246                         "\tmulticast: %i\n"
247                         "\tcached: %i\n",
248                         host_name, port, straddr,
249                         strtxt,
250                         avahi_string_list_get_service_cookie(txt),
251                         !!(flags & AVAHI_LOOKUP_RESULT_LOCAL),
252                         !!(flags & AVAHI_LOOKUP_RESULT_WIDE_AREA),
253                         !!(flags & AVAHI_LOOKUP_RESULT_MULTICAST),
254                         !!(flags & AVAHI_LOOKUP_RESULT_CACHED));
255                 avahi_free(strtxt);
256
257                 // retrieve fingerprint
258                 AvahiStringList *node_name_li = avahi_string_list_find(txt, MESHLINK_MDNS_NAME_KEY);
259                 AvahiStringList *node_fp_li = avahi_string_list_find(txt, MESHLINK_MDNS_FINGERPRINT_KEY);
260
261                 if(node_name_li != NULL && node_fp_li != NULL)
262                 {
263                     char *node_name = (char*)avahi_string_list_get_text(node_name_li) + strlen(MESHLINK_MDNS_NAME_KEY);
264                     char *node_fp = (char*)avahi_string_list_get_text(node_fp_li) + strlen(MESHLINK_MDNS_FINGERPRINT_KEY);
265
266                     if(node_name[0] == '=' && node_fp[0] == '=')
267                     {
268                         node_name += 1;
269                         node_fp += 1;
270
271                         meshlink_node_t *node = meshlink_get_node(mesh, node_name);
272
273                         if(node != NULL)
274                         {
275                             logger(mesh, MESHLINK_INFO, "Node %s is part of the mesh network.\n", node->name);
276
277                             sockaddr_t naddress;
278                             memset(&naddress, 0, sizeof(naddress));
279
280                             switch(address->proto)
281                             {
282                                 case AVAHI_PROTO_INET:
283                                     {
284                                         naddress.in.sin_family = AF_INET;
285                                         naddress.in.sin_port = htons(port);
286                                         naddress.in.sin_addr.s_addr = address->data.ipv4.address;
287                                     }
288                                     break;
289
290                                 case AVAHI_PROTO_INET6:
291                                     {
292                                         naddress.in6.sin6_family = AF_INET6;
293                                         naddress.in6.sin6_port = htons(port);
294                                         memcpy(naddress.in6.sin6_addr.s6_addr, address->data.ipv6.address, sizeof(naddress.in6.sin6_addr.s6_addr));
295                                     }
296                                     break;
297
298                                 default:
299                                     naddress.unknown.family = AF_UNKNOWN;
300                                     break;
301                             }
302
303                             if(naddress.unknown.family != AF_UNKNOWN)
304                             {
305                                 meshlink_hint_address(mesh, (meshlink_node_t *)node, (struct sockaddr*)&naddress);
306                             }
307                             else
308                             {
309                                 logger(mesh, MESHLINK_WARNING, "Could not resolve node %s to a known address family type.\n", node->name);
310                             }
311                         }
312                         else
313                         {
314                             logger(mesh, MESHLINK_WARNING, "Node %s is not part of the mesh network.\n", node_name);
315                         }
316                     }
317                     else
318                     {
319                         logger(mesh, MESHLINK_WARNING, "TXT records invalid.\n");
320                     }
321                 }
322                 else
323                 {
324                     logger(mesh, MESHLINK_WARNING, "TXT records missing.\n");
325                 }
326             }
327             break;
328     }
329
330     avahi_s_service_resolver_free(resolver);
331 }
332
333 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)
334 {
335         meshlink_handle_t *mesh = userdata;
336
337     // asserts
338     assert(mesh != NULL);
339     assert(mesh->avahi_server != NULL);
340     assert(mesh->avahi_poll != NULL);
341
342     /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
343     switch (event)
344     {
345         case AVAHI_BROWSER_FAILURE:
346             {
347                 logger(mesh, MESHLINK_ERROR, "(Browser) %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
348                 avahi_simple_poll_quit(mesh->avahi_poll);
349             }
350             return;
351
352         case AVAHI_BROWSER_NEW:
353             {
354                 // asserts
355                 assert(name != NULL);
356                 assert(type != NULL);
357                 assert(domain != NULL);
358
359                 logger(mesh, MESHLINK_DEBUG, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
360                 /* We ignore the returned resolver object. In the callback
361                    function we free it. Ifthe server is terminated before
362                    the callback function is called the server will free
363                    the resolver for us. */
364                 if(!(avahi_s_service_resolver_new(mesh->avahi_server, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, discovery_resolve_callback, mesh)))
365                 {
366                     logger(mesh, MESHLINK_DEBUG, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_server_errno(mesh->avahi_server)));
367                 }
368             }
369             break;
370
371         case AVAHI_BROWSER_REMOVE:
372             {
373                 // asserts
374                 assert(name != NULL);
375                 assert(type != NULL);
376                 assert(domain != NULL);
377
378                 logger(mesh, MESHLINK_DEBUG, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
379             }
380             break;
381
382         case AVAHI_BROWSER_ALL_FOR_NOW:
383         case AVAHI_BROWSER_CACHE_EXHAUSTED:
384             {
385                 logger(mesh, MESHLINK_DEBUG, "(Browser) %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
386             }
387             break;
388     }
389 }
390
391 static void *discovery_loop(void *userdata)
392 {
393         meshlink_handle_t *mesh = userdata;
394
395     // asserts
396     assert(mesh != NULL);
397     assert(mesh->avahi_poll != NULL);
398
399     avahi_simple_poll_loop(mesh->avahi_poll);
400         
401     return NULL;
402 }
403
404 static void discovery_log_cb(AvahiLogLevel level, const char *txt)
405 {
406     meshlink_log_level_t mlevel = MESHLINK_CRITICAL;
407
408     switch(level)
409     {
410     case AVAHI_LOG_ERROR:
411         mlevel = MESHLINK_ERROR;
412         break;
413
414     case AVAHI_LOG_WARN:
415         mlevel = MESHLINK_WARNING;
416         break;
417
418     case AVAHI_LOG_NOTICE:
419     case AVAHI_LOG_INFO:
420         mlevel = MESHLINK_INFO;
421         break;
422
423     case AVAHI_LOG_DEBUG:
424         mlevel = MESHLINK_DEBUG;
425         break;
426     }
427
428     logger(NULL, mlevel, "%s\n", txt);
429 }
430
431 bool discovery_start(meshlink_handle_t *mesh)
432 {
433     logger(mesh, MESHLINK_DEBUG, "discovery_start called\n");
434
435     // asserts
436     assert(mesh != NULL);
437     assert(mesh->avahi_poll == NULL);
438     assert(mesh->avahi_server == NULL);
439     assert(mesh->avahi_browser == NULL);
440     assert(mesh->discovery_threadstarted == false);
441     assert(mesh->avahi_servicetype == NULL);
442
443     // handle avahi logs
444     avahi_set_log_function(discovery_log_cb);
445     
446     // create service type string
447     size_t servicetype_strlen = sizeof(MESHLINK_MDNS_SERVICE_TYPE) + strlen(mesh->appname) + 1;
448     mesh->avahi_servicetype = malloc(servicetype_strlen);
449
450     if(mesh->avahi_servicetype == NULL)
451     {
452         logger(mesh, MESHLINK_ERROR, "Failed to allocate memory for service type string.\n");
453         goto fail;
454     }
455
456     snprintf(mesh->avahi_servicetype, servicetype_strlen, MESHLINK_MDNS_SERVICE_TYPE, mesh->appname);
457
458     // Allocate discovery loop object
459     if(!(mesh->avahi_poll = avahi_simple_poll_new()))
460     {
461         logger(mesh, MESHLINK_ERROR, "Failed to create discovery poll object.\n");
462                 goto fail;
463     }
464
465     // generate some unique host name (we actually do not care about it)
466     uuid_t hostname;
467     uuid_generate(hostname);
468
469     char hostnamestr[36+1];
470     uuid_unparse_lower(hostname, hostnamestr);
471
472     // Let's set the host name for this server.
473     AvahiServerConfig config;
474     avahi_server_config_init(&config);
475     config.host_name = avahi_strdup(hostnamestr);
476     config.publish_workstation = 0;
477     config.disallow_other_stacks = 0;
478     config.publish_hinfo = 0;
479     config.publish_addresses = 1;
480     config.publish_no_reverse = 1;
481
482     /* Allocate a new server */
483     int error;
484     mesh->avahi_server = avahi_server_new(avahi_simple_poll_get(mesh->avahi_poll), &config, discovery_server_callback, mesh, &error);
485
486     /* Free the configuration data */
487     avahi_server_config_free(&config);
488
489     /* Check wether creating the server object succeeded */
490     if(!mesh->avahi_server)
491     {
492         logger(mesh, MESHLINK_ERROR, "Failed to create discovery server: %s\n", avahi_strerror(error));
493         goto fail;
494     }
495
496     // Create the service browser
497     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)))
498     {
499         logger(mesh, MESHLINK_ERROR, "Failed to create discovery service browser: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
500         goto fail;
501     }
502
503         // Start the discovery thread
504         if(pthread_create(&mesh->discovery_thread, NULL, discovery_loop, mesh) != 0)
505     {
506                 logger(mesh, MESHLINK_ERROR, "Could not start discovery thread: %s\n", strerror(errno));
507                 memset(&mesh->discovery_thread, 0, sizeof mesh->discovery_thread);
508                 goto fail;
509         }
510
511         mesh->discovery_threadstarted = true;
512
513         return true;
514
515 fail:
516     if(mesh->avahi_browser != NULL)
517     {
518         avahi_s_service_browser_free(mesh->avahi_browser);
519         mesh->avahi_browser = NULL;
520     }
521
522     if(mesh->avahi_server != NULL)
523     {
524         avahi_server_free(mesh->avahi_server);
525         mesh->avahi_server = NULL;
526     }
527
528     if(mesh->avahi_poll != NULL)
529     {
530         avahi_simple_poll_free(mesh->avahi_poll);
531         mesh->avahi_poll = NULL;
532     }
533
534     if(mesh->avahi_servicetype != NULL)
535     {
536         free(mesh->avahi_servicetype);
537         mesh->avahi_servicetype = NULL;
538     }
539
540     return false;
541 }
542
543 void discovery_stop(meshlink_handle_t *mesh)
544 {
545     logger(mesh, MESHLINK_DEBUG, "discovery_stop called\n");
546
547     // asserts
548     assert(mesh != NULL);
549
550         // Shut down
551     if(mesh->avahi_poll)
552     {
553         avahi_simple_poll_quit(mesh->avahi_poll);
554     }
555
556         // Wait for the discovery thread to finish
557     if(mesh->discovery_threadstarted == true)
558     {
559         pthread_join(mesh->discovery_thread, NULL);
560         mesh->discovery_threadstarted = false;
561     }
562
563         // Clean up resources
564     if(mesh->avahi_browser != NULL)
565     {
566         avahi_s_service_browser_free(mesh->avahi_browser);
567         mesh->avahi_browser = NULL;
568     }
569
570     if(mesh->avahi_group)
571     {
572         avahi_s_entry_group_reset(mesh->avahi_group);
573         avahi_s_entry_group_free(mesh->avahi_group);
574         mesh->avahi_group = NULL;
575     }
576
577     if(mesh->avahi_server != NULL)
578     {
579         avahi_server_free(mesh->avahi_server);
580         mesh->avahi_server = NULL;
581     }
582
583     if(mesh->avahi_poll != NULL)
584     {
585         avahi_simple_poll_free(mesh->avahi_poll);
586         mesh->avahi_poll = NULL;
587     }
588
589     if(mesh->avahi_servicetype != NULL)
590     {
591         free(mesh->avahi_servicetype);
592         mesh->avahi_servicetype = NULL;
593     }
594 }