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)
25 for(size_t i = 0; i < (size - 1); ++i)
27 buffer[i] = 'a' + (rand() % ('z' - 'a' + 1));
30 buffer[size-1] = '\0';
33 static void discovery_entry_group_callback(CattaServer *server, CattaSEntryGroup *group, CattaEntryGroupState state, void *userdata)
35 meshlink_handle_t *mesh = userdata;
39 assert(mesh->catta_server != NULL);
40 assert(mesh->catta_poll != NULL);
42 pthread_mutex_lock(&(mesh->mesh_mutex));
44 /* Called whenever the entry group state changes */
47 case CATTA_ENTRY_GROUP_ESTABLISHED:
48 /* The entry group has been established successfully */
49 logger(mesh, MESHLINK_DEBUG, "Catta Service successfully established.\n");
52 case CATTA_ENTRY_GROUP_COLLISION:
53 logger(mesh, MESHLINK_WARNING, "Catta Service collision.\n");
54 // @TODO can we just set a new name and retry?
57 case CATTA_ENTRY_GROUP_FAILURE :
58 /* Some kind of failure happened while we were registering our services */
59 logger(mesh, MESHLINK_ERROR, "Catta Entry group failure: %s\n", catta_strerror(catta_server_errno(mesh->catta_server)));
60 catta_simple_poll_quit(mesh->catta_poll);
63 case CATTA_ENTRY_GROUP_UNCOMMITED:
64 case CATTA_ENTRY_GROUP_REGISTERING:
68 pthread_mutex_unlock(&(mesh->mesh_mutex));
72 static void discovery_create_services(meshlink_handle_t *mesh)
74 char *txt_name = NULL;
78 assert(mesh->name != NULL);
79 assert(mesh->myport != NULL);
80 assert(mesh->catta_server != NULL);
81 assert(mesh->catta_poll != NULL);
82 assert(mesh->catta_servicetype != NULL);
83 assert(mesh->self != NULL);
85 pthread_mutex_lock(&(mesh->mesh_mutex));
87 logger(mesh, MESHLINK_DEBUG, "Adding service\n");
89 /* Ifthis is the first time we're called, let's create a new entry group */
90 if(!mesh->catta_group)
92 if(!(mesh->catta_group = catta_s_entry_group_new(mesh->catta_server, discovery_entry_group_callback, mesh)))
94 logger(mesh, MESHLINK_ERROR, "catta_entry_group_new() failed: %s\n", catta_strerror(catta_server_errno(mesh->catta_server)));
99 /* Create txt records */
100 size_t txt_name_len = sizeof(MESHLINK_MDNS_NAME_KEY) + 1 + strlen(mesh->name) + 1;
101 txt_name = malloc(txt_name_len);
105 logger(mesh, MESHLINK_ERROR, "Could not allocate memory for TXT record\n");
109 snprintf(txt_name, txt_name_len, "%s=%s", MESHLINK_MDNS_NAME_KEY, mesh->name);
111 char txt_fingerprint[sizeof(MESHLINK_MDNS_FINGERPRINT_KEY) + 1 + MESHLINK_FINGERPRINTLEN + 1];
112 snprintf(txt_fingerprint, sizeof(txt_fingerprint), "%s=%s", MESHLINK_MDNS_FINGERPRINT_KEY, meshlink_get_fingerprint(mesh, (meshlink_node_t *)mesh->self));
114 /* Add the service */
116 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 logger(mesh, MESHLINK_ERROR, "Failed to add service: %s\n", catta_strerror(ret));
122 /* Tell the server to register the service */
123 if((ret = catta_s_entry_group_commit(mesh->catta_group)) < 0)
125 logger(mesh, MESHLINK_ERROR, "Failed to commit entry_group: %s\n", catta_strerror(ret));
132 catta_simple_poll_quit(mesh->catta_poll);
138 pthread_mutex_unlock(&(mesh->mesh_mutex));
141 static void discovery_server_callback(CattaServer *server, CattaServerState state, void * userdata)
143 meshlink_handle_t *mesh = userdata;
146 assert(mesh != NULL);
148 pthread_mutex_lock(&(mesh->mesh_mutex));
152 case CATTA_SERVER_RUNNING:
154 /* The serve has startup successfully and registered its host
155 * name on the network, so it's time to create our services */
156 if(!mesh->catta_group)
158 discovery_create_services(mesh);
163 case CATTA_SERVER_COLLISION:
166 assert(mesh->catta_server != NULL);
167 assert(mesh->catta_poll != NULL);
169 /* A host name collision happened. Let's pick a new name for the server */
171 generate_rand_string(hostname, sizeof(hostname));
173 logger(mesh, MESHLINK_WARNING, "Catta host name collision, retrying with '%s'\n", hostname);
174 int result = catta_server_set_host_name(mesh->catta_server, hostname);
178 logger(mesh, MESHLINK_ERROR, "Catta failed to set new host name: %s\n", catta_strerror(result));
179 catta_simple_poll_quit(mesh->catta_poll);
184 case CATTA_SERVER_REGISTERING:
186 /* Let's drop our registered services. When the server is back
187 * in CATTA_SERVER_RUNNING state we will register them
188 * again with the new host name. */
189 if(mesh->catta_group)
191 catta_s_entry_group_reset(mesh->catta_group);
192 mesh->catta_group = NULL;
197 case CATTA_SERVER_FAILURE:
200 assert(mesh->catta_server != NULL);
201 assert(mesh->catta_poll != NULL);
203 /* Terminate on failure */
204 logger(mesh, MESHLINK_ERROR, "Catta server failure: %s\n", catta_strerror(catta_server_errno(mesh->catta_server)));
205 catta_simple_poll_quit(mesh->catta_poll);
209 case CATTA_SERVER_INVALID:
213 pthread_mutex_unlock(&(mesh->mesh_mutex));
216 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 meshlink_handle_t *mesh = userdata;
221 assert(resolver != NULL);
222 assert(mesh != NULL);
223 assert(mesh->catta_server != NULL);
225 pthread_mutex_lock(&(mesh->mesh_mutex));
227 /* Called whenever a service has been resolved successfully or timed out */
230 case CATTA_RESOLVER_FAILURE:
233 assert(name != NULL);
234 assert(type != NULL);
235 assert(domain != NULL);
237 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)));
241 case CATTA_RESOLVER_FOUND:
244 assert(name != NULL);
245 assert(type != NULL);
246 assert(domain != NULL);
247 assert(host_name != NULL);
248 assert(address != NULL);
251 char straddr[CATTA_ADDRESS_STR_MAX], *strtxt;
253 logger(mesh, MESHLINK_DEBUG, "(Resolver) Service '%s' of type '%s' in domain '%s':\n", name, type, domain);
255 catta_address_snprint(straddr, sizeof(straddr), address);
256 strtxt = catta_string_list_to_string(txt);
257 logger(mesh, MESHLINK_DEBUG,
265 host_name, port, straddr,
267 catta_string_list_get_service_cookie(txt),
268 !!(flags & CATTA_LOOKUP_RESULT_LOCAL),
269 !!(flags & CATTA_LOOKUP_RESULT_WIDE_AREA),
270 !!(flags & CATTA_LOOKUP_RESULT_MULTICAST),
271 !!(flags & CATTA_LOOKUP_RESULT_CACHED));
274 // retrieve fingerprint
275 CattaStringList *node_name_li = catta_string_list_find(txt, MESHLINK_MDNS_NAME_KEY);
276 CattaStringList *node_fp_li = catta_string_list_find(txt, MESHLINK_MDNS_FINGERPRINT_KEY);
278 if(node_name_li != NULL && node_fp_li != NULL)
280 char *node_name = (char*)catta_string_list_get_text(node_name_li) + strlen(MESHLINK_MDNS_NAME_KEY);
281 char *node_fp = (char*)catta_string_list_get_text(node_fp_li) + strlen(MESHLINK_MDNS_FINGERPRINT_KEY);
283 if(node_name[0] == '=' && node_fp[0] == '=')
288 meshlink_node_t *node = meshlink_get_node(mesh, node_name);
292 logger(mesh, MESHLINK_INFO, "Node %s is part of the mesh network.\n", node->name);
295 memset(&naddress, 0, sizeof(naddress));
297 switch(address->proto)
299 case CATTA_PROTO_INET:
301 naddress.in.sin_family = AF_INET;
302 naddress.in.sin_port = htons(port);
303 naddress.in.sin_addr.s_addr = address->data.ipv4.address;
307 case CATTA_PROTO_INET6:
309 naddress.in6.sin6_family = AF_INET6;
310 naddress.in6.sin6_port = htons(port);
311 memcpy(naddress.in6.sin6_addr.s6_addr, address->data.ipv6.address, sizeof(naddress.in6.sin6_addr.s6_addr));
316 naddress.unknown.family = AF_UNKNOWN;
320 if(naddress.unknown.family != AF_UNKNOWN)
322 meshlink_hint_address(mesh, (meshlink_node_t *)node, (struct sockaddr*)&naddress);
326 logger(mesh, MESHLINK_WARNING, "Could not resolve node %s to a known address family type.\n", node->name);
331 logger(mesh, MESHLINK_WARNING, "Node %s is not part of the mesh network.\n", node_name);
336 logger(mesh, MESHLINK_WARNING, "TXT records invalid.\n");
341 logger(mesh, MESHLINK_WARNING, "TXT records missing.\n");
347 catta_s_service_resolver_free(resolver);
349 pthread_mutex_unlock(&(mesh->mesh_mutex));
352 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 meshlink_handle_t *mesh = userdata;
357 assert(mesh != NULL);
358 assert(mesh->catta_server != NULL);
359 assert(mesh->catta_poll != NULL);
361 pthread_mutex_lock(&(mesh->mesh_mutex));
363 /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
366 case CATTA_BROWSER_FAILURE:
368 logger(mesh, MESHLINK_ERROR, "(Browser) %s\n", catta_strerror(catta_server_errno(mesh->catta_server)));
369 catta_simple_poll_quit(mesh->catta_poll);
373 case CATTA_BROWSER_NEW:
376 assert(name != NULL);
377 assert(type != NULL);
378 assert(domain != NULL);
380 logger(mesh, MESHLINK_DEBUG, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
381 /* We ignore the returned resolver object. In the callback
382 function we free it. Ifthe server is terminated before
383 the callback function is called the server will free
384 the resolver for us. */
385 if(!(catta_s_service_resolver_new(mesh->catta_server, interface_, protocol, name, type, domain, CATTA_PROTO_UNSPEC, 0, discovery_resolve_callback, mesh)))
387 logger(mesh, MESHLINK_DEBUG, "Failed to resolve service '%s': %s\n", name, catta_strerror(catta_server_errno(mesh->catta_server)));
392 case CATTA_BROWSER_REMOVE:
395 assert(name != NULL);
396 assert(type != NULL);
397 assert(domain != NULL);
399 logger(mesh, MESHLINK_DEBUG, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
403 case CATTA_BROWSER_ALL_FOR_NOW:
404 case CATTA_BROWSER_CACHE_EXHAUSTED:
406 logger(mesh, MESHLINK_DEBUG, "(Browser) %s\n", event == CATTA_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
411 pthread_mutex_unlock(&(mesh->mesh_mutex));
414 static void *discovery_loop(void *userdata)
416 meshlink_handle_t *mesh = userdata;
419 assert(mesh != NULL);
420 assert(mesh->catta_poll != NULL);
422 catta_simple_poll_loop(mesh->catta_poll);
427 static void discovery_log_cb(CattaLogLevel level, const char *txt)
429 meshlink_log_level_t mlevel = MESHLINK_CRITICAL;
433 case CATTA_LOG_ERROR:
434 mlevel = MESHLINK_ERROR;
438 mlevel = MESHLINK_WARNING;
441 case CATTA_LOG_NOTICE:
443 mlevel = MESHLINK_INFO;
446 case CATTA_LOG_DEBUG:
447 mlevel = MESHLINK_DEBUG;
451 logger(NULL, mlevel, "%s\n", txt);
454 bool discovery_start(meshlink_handle_t *mesh)
456 logger(mesh, MESHLINK_DEBUG, "discovery_start called\n");
459 assert(mesh != NULL);
460 assert(mesh->catta_poll == NULL);
461 assert(mesh->catta_server == NULL);
462 assert(mesh->catta_browser == NULL);
463 assert(mesh->discovery_threadstarted == false);
464 assert(mesh->catta_servicetype == NULL);
467 catta_set_log_function(discovery_log_cb);
469 // create service type string
470 size_t servicetype_strlen = sizeof(MESHLINK_MDNS_SERVICE_TYPE) + strlen(mesh->appname) + 1;
471 mesh->catta_servicetype = malloc(servicetype_strlen);
473 if(mesh->catta_servicetype == NULL)
475 logger(mesh, MESHLINK_ERROR, "Failed to allocate memory for service type string.\n");
479 snprintf(mesh->catta_servicetype, servicetype_strlen, MESHLINK_MDNS_SERVICE_TYPE, mesh->appname);
481 // Allocate discovery loop object
482 if(!(mesh->catta_poll = catta_simple_poll_new()))
484 logger(mesh, MESHLINK_ERROR, "Failed to create discovery poll object.\n");
488 // generate some unique host name (we actually do not care about it)
490 generate_rand_string(hostname, sizeof(hostname));
492 // Let's set the host name for this server.
493 CattaServerConfig config;
494 catta_server_config_init(&config);
495 config.host_name = catta_strdup(hostname);
496 config.publish_workstation = 0;
497 config.disallow_other_stacks = 0;
498 config.publish_hinfo = 0;
499 config.publish_addresses = 1;
500 config.publish_no_reverse = 1;
502 /* Allocate a new server */
504 mesh->catta_server = catta_server_new(catta_simple_poll_get(mesh->catta_poll), &config, discovery_server_callback, mesh, &error);
506 /* Free the configuration data */
507 catta_server_config_free(&config);
509 /* Check wether creating the server object succeeded */
510 if(!mesh->catta_server)
512 logger(mesh, MESHLINK_ERROR, "Failed to create discovery server: %s\n", catta_strerror(error));
516 // Create the service browser
517 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 logger(mesh, MESHLINK_ERROR, "Failed to create discovery service browser: %s\n", catta_strerror(catta_server_errno(mesh->catta_server)));
523 // Start the discovery thread
524 if(pthread_create(&mesh->discovery_thread, NULL, discovery_loop, mesh) != 0)
526 logger(mesh, MESHLINK_ERROR, "Could not start discovery thread: %s\n", strerror(errno));
527 memset(&mesh->discovery_thread, 0, sizeof mesh->discovery_thread);
531 mesh->discovery_threadstarted = true;
536 if(mesh->catta_browser != NULL)
538 catta_s_service_browser_free(mesh->catta_browser);
539 mesh->catta_browser = NULL;
542 if(mesh->catta_server != NULL)
544 catta_server_free(mesh->catta_server);
545 mesh->catta_server = NULL;
548 if(mesh->catta_poll != NULL)
550 catta_simple_poll_free(mesh->catta_poll);
551 mesh->catta_poll = NULL;
554 if(mesh->catta_servicetype != NULL)
556 free(mesh->catta_servicetype);
557 mesh->catta_servicetype = NULL;
563 void discovery_stop(meshlink_handle_t *mesh)
565 logger(mesh, MESHLINK_DEBUG, "discovery_stop called\n");
568 assert(mesh != NULL);
573 catta_simple_poll_quit(mesh->catta_poll);
576 // Wait for the discovery thread to finish
577 if(mesh->discovery_threadstarted == true)
579 pthread_join(mesh->discovery_thread, NULL);
580 mesh->discovery_threadstarted = false;
583 // Clean up resources
584 if(mesh->catta_browser != NULL)
586 catta_s_service_browser_free(mesh->catta_browser);
587 mesh->catta_browser = NULL;
590 if(mesh->catta_group)
592 catta_s_entry_group_reset(mesh->catta_group);
593 catta_s_entry_group_free(mesh->catta_group);
594 mesh->catta_group = NULL;
597 if(mesh->catta_server != NULL)
599 catta_server_free(mesh->catta_server);
600 mesh->catta_server = NULL;
603 if(mesh->catta_poll != NULL)
605 catta_simple_poll_free(mesh->catta_poll);
606 mesh->catta_poll = NULL;
609 if(mesh->catta_servicetype != NULL)
611 free(mesh->catta_servicetype);
612 mesh->catta_servicetype = NULL;