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));
27 buffer[size-1] = '\0';
30 static void discovery_entry_group_callback(CattaServer *server, CattaSEntryGroup *group, CattaEntryGroupState state, void *userdata) {
31 meshlink_handle_t *mesh = userdata;
35 assert(mesh->catta_server != NULL);
36 assert(mesh->catta_poll != NULL);
38 pthread_mutex_lock(&(mesh->mesh_mutex));
40 /* Called whenever the entry group state changes */
42 case CATTA_ENTRY_GROUP_ESTABLISHED:
43 /* The entry group has been established successfully */
44 logger(mesh, MESHLINK_DEBUG, "Catta Service successfully established.\n");
47 case CATTA_ENTRY_GROUP_COLLISION:
48 logger(mesh, MESHLINK_WARNING, "Catta Service collision.\n");
49 // @TODO can we just set a new name and retry?
52 case CATTA_ENTRY_GROUP_FAILURE :
53 /* Some kind of failure happened while we were registering our services */
54 logger(mesh, MESHLINK_ERROR, "Catta Entry group failure: %s\n", catta_strerror(catta_server_errno(mesh->catta_server)));
55 catta_simple_poll_quit(mesh->catta_poll);
58 case CATTA_ENTRY_GROUP_UNCOMMITED:
59 case CATTA_ENTRY_GROUP_REGISTERING:
63 pthread_mutex_unlock(&(mesh->mesh_mutex));
67 static void discovery_create_services(meshlink_handle_t *mesh) {
68 char *txt_name = NULL;
72 assert(mesh->name != NULL);
73 assert(mesh->myport != NULL);
74 assert(mesh->catta_server != NULL);
75 assert(mesh->catta_poll != NULL);
76 assert(mesh->catta_servicetype != NULL);
77 assert(mesh->self != NULL);
79 pthread_mutex_lock(&(mesh->mesh_mutex));
81 logger(mesh, MESHLINK_DEBUG, "Adding service\n");
83 /* Ifthis is the first time we're called, let's create a new entry group */
84 if(!mesh->catta_group) {
85 if(!(mesh->catta_group = catta_s_entry_group_new(mesh->catta_server, discovery_entry_group_callback, mesh))) {
86 logger(mesh, MESHLINK_ERROR, "catta_entry_group_new() failed: %s\n", catta_strerror(catta_server_errno(mesh->catta_server)));
91 /* Create txt records */
92 size_t txt_name_len = sizeof(MESHLINK_MDNS_NAME_KEY) + 1 + strlen(mesh->name) + 1;
93 txt_name = malloc(txt_name_len);
95 if(txt_name == NULL) {
96 logger(mesh, MESHLINK_ERROR, "Could not allocate memory for TXT record\n");
100 snprintf(txt_name, txt_name_len, "%s=%s", MESHLINK_MDNS_NAME_KEY, mesh->name);
102 char txt_fingerprint[sizeof(MESHLINK_MDNS_FINGERPRINT_KEY) + 1 + MESHLINK_FINGERPRINTLEN + 1];
103 snprintf(txt_fingerprint, sizeof(txt_fingerprint), "%s=%s", MESHLINK_MDNS_FINGERPRINT_KEY, meshlink_get_fingerprint(mesh, (meshlink_node_t *)mesh->self));
105 /* Add the service */
107 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) {
108 logger(mesh, MESHLINK_ERROR, "Failed to add service: %s\n", catta_strerror(ret));
112 /* Tell the server to register the service */
113 if((ret = catta_s_entry_group_commit(mesh->catta_group)) < 0) {
114 logger(mesh, MESHLINK_ERROR, "Failed to commit entry_group: %s\n", catta_strerror(ret));
121 catta_simple_poll_quit(mesh->catta_poll);
127 pthread_mutex_unlock(&(mesh->mesh_mutex));
130 static void discovery_server_callback(CattaServer *server, CattaServerState state, void * userdata) {
131 meshlink_handle_t *mesh = userdata;
134 assert(mesh != NULL);
136 pthread_mutex_lock(&(mesh->mesh_mutex));
139 case CATTA_SERVER_RUNNING: {
140 /* The serve has startup successfully and registered its host
141 * name on the network, so it's time to create our services */
142 if(!mesh->catta_group)
143 discovery_create_services(mesh);
147 case CATTA_SERVER_COLLISION: {
149 assert(mesh->catta_server != NULL);
150 assert(mesh->catta_poll != NULL);
152 /* A host name collision happened. Let's pick a new name for the server */
154 generate_rand_string(hostname, sizeof(hostname));
156 logger(mesh, MESHLINK_WARNING, "Catta host name collision, retrying with '%s'\n", hostname);
157 int result = catta_server_set_host_name(mesh->catta_server, hostname);
160 logger(mesh, MESHLINK_ERROR, "Catta failed to set new host name: %s\n", catta_strerror(result));
161 catta_simple_poll_quit(mesh->catta_poll);
166 case CATTA_SERVER_REGISTERING: {
167 /* Let's drop our registered services. When the server is back
168 * in CATTA_SERVER_RUNNING state we will register them
169 * again with the new host name. */
170 if(mesh->catta_group) {
171 catta_s_entry_group_reset(mesh->catta_group);
172 mesh->catta_group = NULL;
177 case CATTA_SERVER_FAILURE: {
179 assert(mesh->catta_server != NULL);
180 assert(mesh->catta_poll != NULL);
182 /* Terminate on failure */
183 logger(mesh, MESHLINK_ERROR, "Catta server failure: %s\n", catta_strerror(catta_server_errno(mesh->catta_server)));
184 catta_simple_poll_quit(mesh->catta_poll);
188 case CATTA_SERVER_INVALID:
192 pthread_mutex_unlock(&(mesh->mesh_mutex));
195 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) {
196 meshlink_handle_t *mesh = userdata;
199 assert(resolver != NULL);
200 assert(mesh != NULL);
201 assert(mesh->catta_server != NULL);
203 pthread_mutex_lock(&(mesh->mesh_mutex));
205 /* Called whenever a service has been resolved successfully or timed out */
207 case CATTA_RESOLVER_FAILURE: {
209 assert(name != NULL);
210 assert(type != NULL);
211 assert(domain != NULL);
213 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)));
217 case CATTA_RESOLVER_FOUND: {
219 assert(name != NULL);
220 assert(type != NULL);
221 assert(domain != NULL);
222 assert(host_name != NULL);
223 assert(address != NULL);
226 char straddr[CATTA_ADDRESS_STR_MAX], *strtxt;
228 logger(mesh, MESHLINK_DEBUG, "(Resolver) Service '%s' of type '%s' in domain '%s':\n", name, type, domain);
230 catta_address_snprint(straddr, sizeof(straddr), address);
231 strtxt = catta_string_list_to_string(txt);
232 logger(mesh, MESHLINK_DEBUG,
240 host_name, port, straddr,
242 catta_string_list_get_service_cookie(txt),
243 !!(flags & CATTA_LOOKUP_RESULT_LOCAL),
244 !!(flags & CATTA_LOOKUP_RESULT_WIDE_AREA),
245 !!(flags & CATTA_LOOKUP_RESULT_MULTICAST),
246 !!(flags & CATTA_LOOKUP_RESULT_CACHED));
249 // retrieve fingerprint
250 CattaStringList *node_name_li = catta_string_list_find(txt, MESHLINK_MDNS_NAME_KEY);
251 CattaStringList *node_fp_li = catta_string_list_find(txt, MESHLINK_MDNS_FINGERPRINT_KEY);
253 if(node_name_li != NULL && node_fp_li != NULL) {
254 char *node_name = (char*)catta_string_list_get_text(node_name_li) + strlen(MESHLINK_MDNS_NAME_KEY);
255 char *node_fp = (char*)catta_string_list_get_text(node_fp_li) + strlen(MESHLINK_MDNS_FINGERPRINT_KEY);
257 if(node_name[0] == '=' && node_fp[0] == '=') {
261 meshlink_node_t *node = meshlink_get_node(mesh, node_name);
264 logger(mesh, MESHLINK_INFO, "Node %s is part of the mesh network.\n", node->name);
267 memset(&naddress, 0, sizeof(naddress));
269 switch(address->proto) {
270 case CATTA_PROTO_INET: {
271 naddress.in.sin_family = AF_INET;
272 naddress.in.sin_port = htons(port);
273 naddress.in.sin_addr.s_addr = address->data.ipv4.address;
277 case CATTA_PROTO_INET6: {
278 naddress.in6.sin6_family = AF_INET6;
279 naddress.in6.sin6_port = htons(port);
280 memcpy(naddress.in6.sin6_addr.s6_addr, address->data.ipv6.address, sizeof(naddress.in6.sin6_addr.s6_addr));
285 naddress.unknown.family = AF_UNKNOWN;
289 if(naddress.unknown.family != AF_UNKNOWN)
290 meshlink_hint_address(mesh, (meshlink_node_t *)node, (struct sockaddr*)&naddress);
292 logger(mesh, MESHLINK_WARNING, "Could not resolve node %s to a known address family type.\n", node->name);
294 logger(mesh, MESHLINK_WARNING, "Node %s is not part of the mesh network.\n", node_name);
296 logger(mesh, MESHLINK_WARNING, "TXT records invalid.\n");
298 logger(mesh, MESHLINK_WARNING, "TXT records missing.\n");
303 catta_s_service_resolver_free(resolver);
305 pthread_mutex_unlock(&(mesh->mesh_mutex));
308 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) {
309 meshlink_handle_t *mesh = userdata;
312 assert(mesh != NULL);
313 assert(mesh->catta_server != NULL);
314 assert(mesh->catta_poll != NULL);
316 pthread_mutex_lock(&(mesh->mesh_mutex));
318 /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
320 case CATTA_BROWSER_FAILURE: {
321 logger(mesh, MESHLINK_ERROR, "(Browser) %s\n", catta_strerror(catta_server_errno(mesh->catta_server)));
322 catta_simple_poll_quit(mesh->catta_poll);
326 case CATTA_BROWSER_NEW: {
328 assert(name != NULL);
329 assert(type != NULL);
330 assert(domain != NULL);
332 logger(mesh, MESHLINK_DEBUG, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
333 /* We ignore the returned resolver object. In the callback
334 function we free it. Ifthe server is terminated before
335 the callback function is called the server will free
336 the resolver for us. */
337 if(!(catta_s_service_resolver_new(mesh->catta_server, interface_, protocol, name, type, domain, CATTA_PROTO_UNSPEC, 0, discovery_resolve_callback, mesh)))
338 logger(mesh, MESHLINK_DEBUG, "Failed to resolve service '%s': %s\n", name, catta_strerror(catta_server_errno(mesh->catta_server)));
342 case CATTA_BROWSER_REMOVE: {
344 assert(name != NULL);
345 assert(type != NULL);
346 assert(domain != NULL);
348 logger(mesh, MESHLINK_DEBUG, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
352 case CATTA_BROWSER_ALL_FOR_NOW:
353 case CATTA_BROWSER_CACHE_EXHAUSTED: {
354 logger(mesh, MESHLINK_DEBUG, "(Browser) %s\n", event == CATTA_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
359 pthread_mutex_unlock(&(mesh->mesh_mutex));
362 static void *discovery_loop(void *userdata) {
363 meshlink_handle_t *mesh = userdata;
366 assert(mesh != NULL);
367 assert(mesh->catta_poll != NULL);
369 catta_simple_poll_loop(mesh->catta_poll);
374 static void discovery_log_cb(CattaLogLevel level, const char *txt) {
375 meshlink_log_level_t mlevel = MESHLINK_CRITICAL;
378 case CATTA_LOG_ERROR:
379 mlevel = MESHLINK_ERROR;
383 mlevel = MESHLINK_WARNING;
386 case CATTA_LOG_NOTICE:
388 mlevel = MESHLINK_INFO;
391 case CATTA_LOG_DEBUG:
392 mlevel = MESHLINK_DEBUG;
396 logger(NULL, mlevel, "%s\n", txt);
399 bool discovery_start(meshlink_handle_t *mesh) {
400 logger(mesh, MESHLINK_DEBUG, "discovery_start called\n");
403 assert(mesh != NULL);
404 assert(mesh->catta_poll == NULL);
405 assert(mesh->catta_server == NULL);
406 assert(mesh->catta_browser == NULL);
407 assert(mesh->discovery_threadstarted == false);
408 assert(mesh->catta_servicetype == NULL);
411 catta_set_log_function(discovery_log_cb);
413 // create service type string
414 size_t servicetype_strlen = sizeof(MESHLINK_MDNS_SERVICE_TYPE) + strlen(mesh->appname) + 1;
415 mesh->catta_servicetype = malloc(servicetype_strlen);
417 if(mesh->catta_servicetype == NULL) {
418 logger(mesh, MESHLINK_ERROR, "Failed to allocate memory for service type string.\n");
422 snprintf(mesh->catta_servicetype, servicetype_strlen, MESHLINK_MDNS_SERVICE_TYPE, mesh->appname);
424 // Allocate discovery loop object
425 if(!(mesh->catta_poll = catta_simple_poll_new())) {
426 logger(mesh, MESHLINK_ERROR, "Failed to create discovery poll object.\n");
430 // generate some unique host name (we actually do not care about it)
432 generate_rand_string(hostname, sizeof(hostname));
434 // Let's set the host name for this server.
435 CattaServerConfig config;
436 catta_server_config_init(&config);
437 config.host_name = catta_strdup(hostname);
438 config.publish_workstation = 0;
439 config.disallow_other_stacks = 0;
440 config.publish_hinfo = 0;
441 config.publish_addresses = 1;
442 config.publish_no_reverse = 1;
444 /* Allocate a new server */
446 mesh->catta_server = catta_server_new(catta_simple_poll_get(mesh->catta_poll), &config, discovery_server_callback, mesh, &error);
448 /* Free the configuration data */
449 catta_server_config_free(&config);
451 /* Check wether creating the server object succeeded */
452 if(!mesh->catta_server) {
453 logger(mesh, MESHLINK_ERROR, "Failed to create discovery server: %s\n", catta_strerror(error));
457 // Create the service browser
458 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))) {
459 logger(mesh, MESHLINK_ERROR, "Failed to create discovery service browser: %s\n", catta_strerror(catta_server_errno(mesh->catta_server)));
463 // Start the discovery thread
464 if(pthread_create(&mesh->discovery_thread, NULL, discovery_loop, mesh) != 0) {
465 logger(mesh, MESHLINK_ERROR, "Could not start discovery thread: %s\n", strerror(errno));
466 memset(&mesh->discovery_thread, 0, sizeof mesh->discovery_thread);
470 mesh->discovery_threadstarted = true;
475 if(mesh->catta_browser != NULL) {
476 catta_s_service_browser_free(mesh->catta_browser);
477 mesh->catta_browser = NULL;
480 if(mesh->catta_server != NULL) {
481 catta_server_free(mesh->catta_server);
482 mesh->catta_server = NULL;
485 if(mesh->catta_poll != NULL) {
486 catta_simple_poll_free(mesh->catta_poll);
487 mesh->catta_poll = NULL;
490 if(mesh->catta_servicetype != NULL) {
491 free(mesh->catta_servicetype);
492 mesh->catta_servicetype = NULL;
498 void discovery_stop(meshlink_handle_t *mesh) {
499 logger(mesh, MESHLINK_DEBUG, "discovery_stop called\n");
502 assert(mesh != NULL);
506 catta_simple_poll_quit(mesh->catta_poll);
508 // Wait for the discovery thread to finish
509 if(mesh->discovery_threadstarted == true) {
510 pthread_join(mesh->discovery_thread, NULL);
511 mesh->discovery_threadstarted = false;
514 // Clean up resources
515 if(mesh->catta_browser != NULL) {
516 catta_s_service_browser_free(mesh->catta_browser);
517 mesh->catta_browser = NULL;
520 if(mesh->catta_group) {
521 catta_s_entry_group_reset(mesh->catta_group);
522 catta_s_entry_group_free(mesh->catta_group);
523 mesh->catta_group = NULL;
526 if(mesh->catta_server != NULL) {
527 catta_server_free(mesh->catta_server);
528 mesh->catta_server = NULL;
531 if(mesh->catta_poll != NULL) {
532 catta_simple_poll_free(mesh->catta_poll);
533 mesh->catta_poll = NULL;
536 if(mesh->catta_servicetype != NULL) {
537 free(mesh->catta_servicetype);
538 mesh->catta_servicetype = NULL;