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