1 #include <catta/core.h>
2 #include <catta/lookup.h>
3 #include <catta/publish.h>
5 #include <catta/simple-watch.h>
6 #include <catta/malloc.h>
7 #include <catta/alternative.h>
8 #include <catta/error.h>
10 #include "meshlink_internal.h"
11 #include "discovery.h"
17 #include <netinet/in.h>
19 #define MESHLINK_MDNS_SERVICE_TYPE "_%s._tcp"
20 #define MESHLINK_MDNS_NAME_KEY "name"
21 #define MESHLINK_MDNS_FINGERPRINT_KEY "fingerprint"
23 static void generate_rand_string(char *buffer, size_t size) {
24 for(size_t i = 0; i < (size - 1); ++i) {
25 buffer[i] = 'a' + (rand() % ('z' - 'a' + 1));
28 buffer[size - 1] = '\0';
31 static void discovery_entry_group_callback(CattaServer *server, CattaSEntryGroup *group, CattaEntryGroupState state, void *userdata) {
34 meshlink_handle_t *mesh = userdata;
38 assert(mesh->catta_server != NULL);
39 assert(mesh->catta_poll != NULL);
41 pthread_mutex_lock(&(mesh->mesh_mutex));
43 /* Called whenever the entry group state changes */
45 case CATTA_ENTRY_GROUP_ESTABLISHED:
46 /* The entry group has been established successfully */
47 logger(mesh, MESHLINK_DEBUG, "Catta Service successfully established.\n");
50 case CATTA_ENTRY_GROUP_COLLISION:
51 logger(mesh, MESHLINK_WARNING, "Catta Service collision.\n");
52 // @TODO can we just set a new name and retry?
55 case CATTA_ENTRY_GROUP_FAILURE :
56 /* Some kind of failure happened while we were registering our services */
57 logger(mesh, MESHLINK_ERROR, "Catta Entry group failure: %s\n", catta_strerror(catta_server_errno(mesh->catta_server)));
58 catta_simple_poll_quit(mesh->catta_poll);
61 case CATTA_ENTRY_GROUP_UNCOMMITED:
62 case CATTA_ENTRY_GROUP_REGISTERING:
66 pthread_mutex_unlock(&(mesh->mesh_mutex));
70 static void discovery_create_services(meshlink_handle_t *mesh) {
71 char *txt_name = NULL;
75 assert(mesh->name != NULL);
76 assert(mesh->myport != NULL);
77 assert(mesh->catta_server != NULL);
78 assert(mesh->catta_poll != NULL);
79 assert(mesh->catta_servicetype != NULL);
80 assert(mesh->self != NULL);
82 pthread_mutex_lock(&(mesh->mesh_mutex));
84 logger(mesh, MESHLINK_DEBUG, "Adding service\n");
86 /* Ifthis is the first time we're called, let's create a new entry group */
87 if(!mesh->catta_group) {
88 if(!(mesh->catta_group = catta_s_entry_group_new(mesh->catta_server, discovery_entry_group_callback, mesh))) {
89 logger(mesh, MESHLINK_ERROR, "catta_entry_group_new() failed: %s\n", catta_strerror(catta_server_errno(mesh->catta_server)));
94 /* Create txt records */
95 size_t txt_name_len = sizeof(MESHLINK_MDNS_NAME_KEY) + 1 + strlen(mesh->name) + 1;
96 txt_name = malloc(txt_name_len);
98 if(txt_name == NULL) {
99 logger(mesh, MESHLINK_ERROR, "Could not allocate memory for TXT record\n");
103 snprintf(txt_name, txt_name_len, "%s=%s", MESHLINK_MDNS_NAME_KEY, mesh->name);
105 char txt_fingerprint[sizeof(MESHLINK_MDNS_FINGERPRINT_KEY) + 1 + MESHLINK_FINGERPRINTLEN + 1];
106 snprintf(txt_fingerprint, sizeof(txt_fingerprint), "%s=%s", MESHLINK_MDNS_FINGERPRINT_KEY, meshlink_get_fingerprint(mesh, (meshlink_node_t *)mesh->self));
108 /* Add the service */
111 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) {
112 logger(mesh, MESHLINK_ERROR, "Failed to add service: %s\n", catta_strerror(ret));
116 /* Tell the server to register the service */
117 if((ret = catta_s_entry_group_commit(mesh->catta_group)) < 0) {
118 logger(mesh, MESHLINK_ERROR, "Failed to commit entry_group: %s\n", catta_strerror(ret));
125 catta_simple_poll_quit(mesh->catta_poll);
133 pthread_mutex_unlock(&(mesh->mesh_mutex));
136 static void discovery_server_callback(CattaServer *server, CattaServerState state, void *userdata) {
138 meshlink_handle_t *mesh = userdata;
141 assert(mesh != NULL);
143 pthread_mutex_lock(&(mesh->mesh_mutex));
146 case CATTA_SERVER_RUNNING: {
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->catta_group) {
150 discovery_create_services(mesh);
155 case CATTA_SERVER_COLLISION: {
157 assert(mesh->catta_server != NULL);
158 assert(mesh->catta_poll != NULL);
160 /* A host name collision happened. Let's pick a new name for the server */
162 generate_rand_string(hostname, sizeof(hostname));
164 logger(mesh, MESHLINK_WARNING, "Catta host name collision, retrying with '%s'\n", hostname);
165 int result = catta_server_set_host_name(mesh->catta_server, hostname);
168 logger(mesh, MESHLINK_ERROR, "Catta failed to set new host name: %s\n", catta_strerror(result));
169 catta_simple_poll_quit(mesh->catta_poll);
174 case CATTA_SERVER_REGISTERING: {
175 /* Let's drop our registered services. When the server is back
176 * in CATTA_SERVER_RUNNING state we will register them
177 * again with the new host name. */
178 if(mesh->catta_group) {
179 catta_s_entry_group_reset(mesh->catta_group);
180 mesh->catta_group = NULL;
185 case CATTA_SERVER_FAILURE: {
187 assert(mesh->catta_server != NULL);
188 assert(mesh->catta_poll != NULL);
190 /* Terminate on failure */
191 logger(mesh, MESHLINK_ERROR, "Catta server failure: %s\n", catta_strerror(catta_server_errno(mesh->catta_server)));
192 catta_simple_poll_quit(mesh->catta_poll);
196 case CATTA_SERVER_INVALID:
200 pthread_mutex_unlock(&(mesh->mesh_mutex));
203 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) {
206 meshlink_handle_t *mesh = userdata;
209 assert(resolver != NULL);
210 assert(mesh != NULL);
211 assert(mesh->catta_server != NULL);
213 pthread_mutex_lock(&(mesh->mesh_mutex));
215 /* Called whenever a service has been resolved successfully or timed out */
217 case CATTA_RESOLVER_FAILURE: {
219 assert(name != NULL);
220 assert(type != NULL);
221 assert(domain != NULL);
223 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)));
227 case CATTA_RESOLVER_FOUND: {
229 assert(name != NULL);
230 assert(type != NULL);
231 assert(domain != NULL);
232 assert(host_name != NULL);
233 assert(address != NULL);
236 char straddr[CATTA_ADDRESS_STR_MAX], *strtxt;
238 logger(mesh, MESHLINK_DEBUG, "(Resolver) Service '%s' of type '%s' in domain '%s':\n", name, type, domain);
240 catta_address_snprint(straddr, sizeof(straddr), address);
241 strtxt = catta_string_list_to_string(txt);
242 logger(mesh, MESHLINK_DEBUG,
250 host_name, port, straddr,
252 catta_string_list_get_service_cookie(txt),
253 !!(flags & CATTA_LOOKUP_RESULT_LOCAL),
254 !!(flags & CATTA_LOOKUP_RESULT_WIDE_AREA),
255 !!(flags & CATTA_LOOKUP_RESULT_MULTICAST),
256 !!(flags & CATTA_LOOKUP_RESULT_CACHED));
259 // retrieve fingerprint
260 CattaStringList *node_name_li = catta_string_list_find(txt, MESHLINK_MDNS_NAME_KEY);
261 CattaStringList *node_fp_li = catta_string_list_find(txt, MESHLINK_MDNS_FINGERPRINT_KEY);
263 if(node_name_li != NULL && node_fp_li != NULL) {
264 char *node_name = (char *)catta_string_list_get_text(node_name_li) + strlen(MESHLINK_MDNS_NAME_KEY);
265 char *node_fp = (char *)catta_string_list_get_text(node_fp_li) + strlen(MESHLINK_MDNS_FINGERPRINT_KEY);
267 if(node_name[0] == '=' && node_fp[0] == '=') {
271 meshlink_node_t *node = meshlink_get_node(mesh, node_name);
274 logger(mesh, MESHLINK_INFO, "Node %s is part of the mesh network.\n", node->name);
277 memset(&naddress, 0, sizeof(naddress));
279 switch(address->proto) {
280 case CATTA_PROTO_INET: {
281 naddress.in.sin_family = AF_INET;
282 naddress.in.sin_port = htons(port);
283 naddress.in.sin_addr.s_addr = address->data.ipv4.address;
287 case CATTA_PROTO_INET6: {
288 naddress.in6.sin6_family = AF_INET6;
289 naddress.in6.sin6_port = htons(port);
290 memcpy(naddress.in6.sin6_addr.s6_addr, address->data.ipv6.address, sizeof(naddress.in6.sin6_addr.s6_addr));
295 naddress.unknown.family = AF_UNKNOWN;
299 if(naddress.unknown.family != AF_UNKNOWN) {
300 meshlink_hint_address(mesh, (meshlink_node_t *)node, (struct sockaddr *)&naddress);
302 logger(mesh, MESHLINK_WARNING, "Could not resolve node %s to a known address family type.\n", node->name);
305 logger(mesh, MESHLINK_WARNING, "Node %s is not part of the mesh network.\n", node_name);
308 logger(mesh, MESHLINK_WARNING, "TXT records invalid.\n");
311 logger(mesh, MESHLINK_WARNING, "TXT records missing.\n");
317 catta_s_service_resolver_free(resolver);
319 pthread_mutex_unlock(&(mesh->mesh_mutex));
322 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) {
325 meshlink_handle_t *mesh = userdata;
328 assert(mesh != NULL);
329 assert(mesh->catta_server != NULL);
330 assert(mesh->catta_poll != NULL);
332 pthread_mutex_lock(&(mesh->mesh_mutex));
334 /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
336 case CATTA_BROWSER_FAILURE: {
337 logger(mesh, MESHLINK_ERROR, "(Browser) %s\n", catta_strerror(catta_server_errno(mesh->catta_server)));
338 catta_simple_poll_quit(mesh->catta_poll);
342 case CATTA_BROWSER_NEW: {
344 assert(name != NULL);
345 assert(type != NULL);
346 assert(domain != NULL);
348 logger(mesh, MESHLINK_DEBUG, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
350 /* We ignore the returned resolver object. In the callback
351 function we free it. Ifthe server is terminated before
352 the callback function is called the server will free
353 the resolver for us. */
354 if(!(catta_s_service_resolver_new(mesh->catta_server, interface_, protocol, name, type, domain, CATTA_PROTO_UNSPEC, 0, discovery_resolve_callback, mesh))) {
355 logger(mesh, MESHLINK_DEBUG, "Failed to resolve service '%s': %s\n", name, catta_strerror(catta_server_errno(mesh->catta_server)));
360 case CATTA_BROWSER_REMOVE: {
362 assert(name != NULL);
363 assert(type != NULL);
364 assert(domain != NULL);
366 logger(mesh, MESHLINK_DEBUG, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
370 case CATTA_BROWSER_ALL_FOR_NOW:
371 case CATTA_BROWSER_CACHE_EXHAUSTED: {
372 logger(mesh, MESHLINK_DEBUG, "(Browser) %s\n", event == CATTA_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
377 pthread_mutex_unlock(&(mesh->mesh_mutex));
380 static void *discovery_loop(void *userdata) {
381 meshlink_handle_t *mesh = userdata;
384 assert(mesh != NULL);
385 assert(mesh->catta_poll != NULL);
387 catta_simple_poll_loop(mesh->catta_poll);
392 static void discovery_log_cb(CattaLogLevel level, const char *txt) {
393 meshlink_log_level_t mlevel = MESHLINK_CRITICAL;
396 case CATTA_LOG_ERROR:
397 mlevel = MESHLINK_ERROR;
401 mlevel = MESHLINK_WARNING;
404 case CATTA_LOG_NOTICE:
406 mlevel = MESHLINK_INFO;
409 case CATTA_LOG_DEBUG:
411 mlevel = MESHLINK_DEBUG;
415 logger(NULL, mlevel, "%s\n", txt);
418 bool discovery_start(meshlink_handle_t *mesh) {
419 logger(mesh, MESHLINK_DEBUG, "discovery_start called\n");
422 assert(mesh != NULL);
423 assert(mesh->catta_poll == NULL);
424 assert(mesh->catta_server == NULL);
425 assert(mesh->catta_browser == NULL);
426 assert(mesh->discovery_threadstarted == false);
427 assert(mesh->catta_servicetype == NULL);
430 catta_set_log_function(discovery_log_cb);
432 // create service type string
433 size_t servicetype_strlen = sizeof(MESHLINK_MDNS_SERVICE_TYPE) + strlen(mesh->appname) + 1;
434 mesh->catta_servicetype = malloc(servicetype_strlen);
436 if(mesh->catta_servicetype == NULL) {
437 logger(mesh, MESHLINK_ERROR, "Failed to allocate memory for service type string.\n");
441 snprintf(mesh->catta_servicetype, servicetype_strlen, MESHLINK_MDNS_SERVICE_TYPE, mesh->appname);
443 // Allocate discovery loop object
444 if(!(mesh->catta_poll = catta_simple_poll_new())) {
445 logger(mesh, MESHLINK_ERROR, "Failed to create discovery poll object.\n");
449 // generate some unique host name (we actually do not care about it)
451 generate_rand_string(hostname, sizeof(hostname));
453 // Let's set the host name for this server.
454 CattaServerConfig config;
455 catta_server_config_init(&config);
456 config.host_name = catta_strdup(hostname);
457 config.publish_workstation = 0;
458 config.disallow_other_stacks = 0;
459 config.publish_hinfo = 0;
460 config.publish_addresses = 1;
461 config.publish_no_reverse = 1;
463 /* Allocate a new server */
465 mesh->catta_server = catta_server_new(catta_simple_poll_get(mesh->catta_poll), &config, discovery_server_callback, mesh, &error);
467 /* Free the configuration data */
468 catta_server_config_free(&config);
470 /* Check wether creating the server object succeeded */
471 if(!mesh->catta_server) {
472 logger(mesh, MESHLINK_ERROR, "Failed to create discovery server: %s\n", catta_strerror(error));
476 // Create the service browser
477 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))) {
478 logger(mesh, MESHLINK_ERROR, "Failed to create discovery service browser: %s\n", catta_strerror(catta_server_errno(mesh->catta_server)));
482 // Start the discovery thread
483 if(pthread_create(&mesh->discovery_thread, NULL, discovery_loop, mesh) != 0) {
484 logger(mesh, MESHLINK_ERROR, "Could not start discovery thread: %s\n", strerror(errno));
485 memset(&mesh->discovery_thread, 0, sizeof(mesh)->discovery_thread);
489 mesh->discovery_threadstarted = true;
495 if(mesh->catta_browser != NULL) {
496 catta_s_service_browser_free(mesh->catta_browser);
497 mesh->catta_browser = NULL;
500 if(mesh->catta_server != NULL) {
501 catta_server_free(mesh->catta_server);
502 mesh->catta_server = NULL;
505 if(mesh->catta_poll != NULL) {
506 catta_simple_poll_free(mesh->catta_poll);
507 mesh->catta_poll = NULL;
510 if(mesh->catta_servicetype != NULL) {
511 free(mesh->catta_servicetype);
512 mesh->catta_servicetype = NULL;
518 void discovery_stop(meshlink_handle_t *mesh) {
519 logger(mesh, MESHLINK_DEBUG, "discovery_stop called\n");
522 assert(mesh != NULL);
525 if(mesh->catta_poll) {
526 catta_simple_poll_quit(mesh->catta_poll);
529 // Wait for the discovery thread to finish
530 if(mesh->discovery_threadstarted == true) {
531 pthread_join(mesh->discovery_thread, NULL);
532 mesh->discovery_threadstarted = false;
535 // Clean up resources
536 if(mesh->catta_browser != NULL) {
537 catta_s_service_browser_free(mesh->catta_browser);
538 mesh->catta_browser = NULL;
541 if(mesh->catta_group) {
542 catta_s_entry_group_reset(mesh->catta_group);
543 catta_s_entry_group_free(mesh->catta_group);
544 mesh->catta_group = NULL;
547 if(mesh->catta_server != NULL) {
548 catta_server_free(mesh->catta_server);
549 mesh->catta_server = NULL;
552 if(mesh->catta_poll != NULL) {
553 catta_simple_poll_free(mesh->catta_poll);
554 mesh->catta_poll = NULL;
557 if(mesh->catta_servicetype != NULL) {
558 free(mesh->catta_servicetype);
559 mesh->catta_servicetype = NULL;