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) {
33 meshlink_handle_t *mesh = userdata;
37 assert(mesh->catta_server != NULL);
38 assert(mesh->catta_poll != NULL);
40 pthread_mutex_lock(&(mesh->mesh_mutex));
42 /* Called whenever the entry group state changes */
44 case CATTA_ENTRY_GROUP_ESTABLISHED:
45 /* The entry group has been established successfully */
46 logger(mesh, MESHLINK_DEBUG, "Catta Service successfully established.\n");
49 case CATTA_ENTRY_GROUP_COLLISION:
50 logger(mesh, MESHLINK_WARNING, "Catta Service collision.\n");
51 // @TODO can we just set a new name and retry?
54 case CATTA_ENTRY_GROUP_FAILURE :
55 /* Some kind of failure happened while we were registering our services */
56 logger(mesh, MESHLINK_ERROR, "Catta Entry group failure: %s\n", catta_strerror(catta_server_errno(mesh->catta_server)));
57 catta_simple_poll_quit(mesh->catta_poll);
60 case CATTA_ENTRY_GROUP_UNCOMMITED:
61 case CATTA_ENTRY_GROUP_REGISTERING:
65 pthread_mutex_unlock(&(mesh->mesh_mutex));
69 static void discovery_create_services(meshlink_handle_t *mesh) {
70 char *txt_name = NULL;
74 assert(mesh->name != NULL);
75 assert(mesh->myport != NULL);
76 assert(mesh->catta_server != NULL);
77 assert(mesh->catta_poll != NULL);
78 assert(mesh->catta_servicetype != NULL);
79 assert(mesh->self != NULL);
81 pthread_mutex_lock(&(mesh->mesh_mutex));
83 logger(mesh, MESHLINK_DEBUG, "Adding service\n");
85 /* Ifthis is the first time we're called, let's create a new entry group */
86 if(!mesh->catta_group) {
87 if(!(mesh->catta_group = catta_s_entry_group_new(mesh->catta_server, discovery_entry_group_callback, mesh))) {
88 logger(mesh, MESHLINK_ERROR, "catta_entry_group_new() failed: %s\n", catta_strerror(catta_server_errno(mesh->catta_server)));
93 /* Create txt records */
94 size_t txt_name_len = sizeof(MESHLINK_MDNS_NAME_KEY) + 1 + strlen(mesh->name) + 1;
95 txt_name = malloc(txt_name_len);
97 if(txt_name == NULL) {
98 logger(mesh, MESHLINK_ERROR, "Could not allocate memory for TXT record\n");
102 snprintf(txt_name, txt_name_len, "%s=%s", MESHLINK_MDNS_NAME_KEY, mesh->name);
104 char txt_fingerprint[sizeof(MESHLINK_MDNS_FINGERPRINT_KEY) + 1 + MESHLINK_FINGERPRINTLEN + 1];
105 snprintf(txt_fingerprint, sizeof(txt_fingerprint), "%s=%s", MESHLINK_MDNS_FINGERPRINT_KEY, meshlink_get_fingerprint(mesh, (meshlink_node_t *)mesh->self));
107 /* Add the service */
109 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) {
110 logger(mesh, MESHLINK_ERROR, "Failed to add service: %s\n", catta_strerror(ret));
114 /* Tell the server to register the service */
115 if((ret = catta_s_entry_group_commit(mesh->catta_group)) < 0) {
116 logger(mesh, MESHLINK_ERROR, "Failed to commit entry_group: %s\n", catta_strerror(ret));
123 catta_simple_poll_quit(mesh->catta_poll);
129 pthread_mutex_unlock(&(mesh->mesh_mutex));
132 static void discovery_server_callback(CattaServer *server, CattaServerState state, void *userdata) {
134 meshlink_handle_t *mesh = userdata;
137 assert(mesh != NULL);
139 pthread_mutex_lock(&(mesh->mesh_mutex));
142 case CATTA_SERVER_RUNNING: {
143 /* The serve has startup successfully and registered its host
144 * name on the network, so it's time to create our services */
145 if(!mesh->catta_group)
146 discovery_create_services(mesh);
150 case CATTA_SERVER_COLLISION: {
152 assert(mesh->catta_server != NULL);
153 assert(mesh->catta_poll != NULL);
155 /* A host name collision happened. Let's pick a new name for the server */
157 generate_rand_string(hostname, sizeof(hostname));
159 logger(mesh, MESHLINK_WARNING, "Catta host name collision, retrying with '%s'\n", hostname);
160 int result = catta_server_set_host_name(mesh->catta_server, hostname);
163 logger(mesh, MESHLINK_ERROR, "Catta failed to set new host name: %s\n", catta_strerror(result));
164 catta_simple_poll_quit(mesh->catta_poll);
169 case CATTA_SERVER_REGISTERING: {
170 /* Let's drop our registered services. When the server is back
171 * in CATTA_SERVER_RUNNING state we will register them
172 * again with the new host name. */
173 if(mesh->catta_group) {
174 catta_s_entry_group_reset(mesh->catta_group);
175 mesh->catta_group = NULL;
180 case CATTA_SERVER_FAILURE: {
182 assert(mesh->catta_server != NULL);
183 assert(mesh->catta_poll != NULL);
185 /* Terminate on failure */
186 logger(mesh, MESHLINK_ERROR, "Catta server failure: %s\n", catta_strerror(catta_server_errno(mesh->catta_server)));
187 catta_simple_poll_quit(mesh->catta_poll);
191 case CATTA_SERVER_INVALID:
195 pthread_mutex_unlock(&(mesh->mesh_mutex));
198 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) {
201 meshlink_handle_t *mesh = userdata;
204 assert(resolver != NULL);
205 assert(mesh != NULL);
206 assert(mesh->catta_server != NULL);
208 pthread_mutex_lock(&(mesh->mesh_mutex));
210 /* Called whenever a service has been resolved successfully or timed out */
212 case CATTA_RESOLVER_FAILURE: {
214 assert(name != NULL);
215 assert(type != NULL);
216 assert(domain != NULL);
218 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)));
222 case CATTA_RESOLVER_FOUND: {
224 assert(name != NULL);
225 assert(type != NULL);
226 assert(domain != NULL);
227 assert(host_name != NULL);
228 assert(address != NULL);
231 char straddr[CATTA_ADDRESS_STR_MAX], *strtxt;
233 logger(mesh, MESHLINK_DEBUG, "(Resolver) Service '%s' of type '%s' in domain '%s':\n", name, type, domain);
235 catta_address_snprint(straddr, sizeof(straddr), address);
236 strtxt = catta_string_list_to_string(txt);
237 logger(mesh, MESHLINK_DEBUG,
245 host_name, port, straddr,
247 catta_string_list_get_service_cookie(txt),
248 !!(flags & CATTA_LOOKUP_RESULT_LOCAL),
249 !!(flags & CATTA_LOOKUP_RESULT_WIDE_AREA),
250 !!(flags & CATTA_LOOKUP_RESULT_MULTICAST),
251 !!(flags & CATTA_LOOKUP_RESULT_CACHED));
254 // retrieve fingerprint
255 CattaStringList *node_name_li = catta_string_list_find(txt, MESHLINK_MDNS_NAME_KEY);
256 CattaStringList *node_fp_li = catta_string_list_find(txt, MESHLINK_MDNS_FINGERPRINT_KEY);
258 if(node_name_li != NULL && node_fp_li != NULL) {
259 char *node_name = (char *)catta_string_list_get_text(node_name_li) + strlen(MESHLINK_MDNS_NAME_KEY);
260 char *node_fp = (char *)catta_string_list_get_text(node_fp_li) + strlen(MESHLINK_MDNS_FINGERPRINT_KEY);
262 if(node_name[0] == '=' && node_fp[0] == '=') {
266 meshlink_node_t *node = meshlink_get_node(mesh, node_name);
269 logger(mesh, MESHLINK_INFO, "Node %s is part of the mesh network.\n", node->name);
272 memset(&naddress, 0, sizeof(naddress));
274 switch(address->proto) {
275 case CATTA_PROTO_INET: {
276 naddress.in.sin_family = AF_INET;
277 naddress.in.sin_port = htons(port);
278 naddress.in.sin_addr.s_addr = address->data.ipv4.address;
282 case CATTA_PROTO_INET6: {
283 naddress.in6.sin6_family = AF_INET6;
284 naddress.in6.sin6_port = htons(port);
285 memcpy(naddress.in6.sin6_addr.s6_addr, address->data.ipv6.address, sizeof(naddress.in6.sin6_addr.s6_addr));
290 naddress.unknown.family = AF_UNKNOWN;
294 if(naddress.unknown.family != AF_UNKNOWN)
295 meshlink_hint_address(mesh, (meshlink_node_t *)node, (struct sockaddr *)&naddress);
297 logger(mesh, MESHLINK_WARNING, "Could not resolve node %s to a known address family type.\n", node->name);
299 logger(mesh, MESHLINK_WARNING, "Node %s is not part of the mesh network.\n", node_name);
301 logger(mesh, MESHLINK_WARNING, "TXT records invalid.\n");
303 logger(mesh, MESHLINK_WARNING, "TXT records missing.\n");
308 catta_s_service_resolver_free(resolver);
310 pthread_mutex_unlock(&(mesh->mesh_mutex));
313 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) {
316 meshlink_handle_t *mesh = userdata;
319 assert(mesh != NULL);
320 assert(mesh->catta_server != NULL);
321 assert(mesh->catta_poll != NULL);
323 pthread_mutex_lock(&(mesh->mesh_mutex));
325 /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
327 case CATTA_BROWSER_FAILURE: {
328 logger(mesh, MESHLINK_ERROR, "(Browser) %s\n", catta_strerror(catta_server_errno(mesh->catta_server)));
329 catta_simple_poll_quit(mesh->catta_poll);
333 case CATTA_BROWSER_NEW: {
335 assert(name != NULL);
336 assert(type != NULL);
337 assert(domain != NULL);
339 logger(mesh, MESHLINK_DEBUG, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
340 /* We ignore the returned resolver object. In the callback
341 function we free it. Ifthe server is terminated before
342 the callback function is called the server will free
343 the resolver for us. */
344 if(!(catta_s_service_resolver_new(mesh->catta_server, interface_, protocol, name, type, domain, CATTA_PROTO_UNSPEC, 0, discovery_resolve_callback, mesh)))
345 logger(mesh, MESHLINK_DEBUG, "Failed to resolve service '%s': %s\n", name, catta_strerror(catta_server_errno(mesh->catta_server)));
349 case CATTA_BROWSER_REMOVE: {
351 assert(name != NULL);
352 assert(type != NULL);
353 assert(domain != NULL);
355 logger(mesh, MESHLINK_DEBUG, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
359 case CATTA_BROWSER_ALL_FOR_NOW:
360 case CATTA_BROWSER_CACHE_EXHAUSTED: {
361 logger(mesh, MESHLINK_DEBUG, "(Browser) %s\n", event == CATTA_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
366 pthread_mutex_unlock(&(mesh->mesh_mutex));
369 static void *discovery_loop(void *userdata) {
370 meshlink_handle_t *mesh = userdata;
373 assert(mesh != NULL);
374 assert(mesh->catta_poll != NULL);
376 catta_simple_poll_loop(mesh->catta_poll);
381 static void discovery_log_cb(CattaLogLevel level, const char *txt) {
382 meshlink_log_level_t mlevel = MESHLINK_CRITICAL;
385 case CATTA_LOG_ERROR:
386 mlevel = MESHLINK_ERROR;
390 mlevel = MESHLINK_WARNING;
393 case CATTA_LOG_NOTICE:
395 mlevel = MESHLINK_INFO;
398 case CATTA_LOG_DEBUG:
400 mlevel = MESHLINK_DEBUG;
404 logger(NULL, mlevel, "%s\n", txt);
407 bool discovery_start(meshlink_handle_t *mesh) {
408 logger(mesh, MESHLINK_DEBUG, "discovery_start called\n");
411 assert(mesh != NULL);
412 assert(mesh->catta_poll == NULL);
413 assert(mesh->catta_server == NULL);
414 assert(mesh->catta_browser == NULL);
415 assert(mesh->discovery_threadstarted == false);
416 assert(mesh->catta_servicetype == NULL);
419 catta_set_log_function(discovery_log_cb);
421 // create service type string
422 size_t servicetype_strlen = sizeof(MESHLINK_MDNS_SERVICE_TYPE) + strlen(mesh->appname) + 1;
423 mesh->catta_servicetype = malloc(servicetype_strlen);
425 if(mesh->catta_servicetype == NULL) {
426 logger(mesh, MESHLINK_ERROR, "Failed to allocate memory for service type string.\n");
430 snprintf(mesh->catta_servicetype, servicetype_strlen, MESHLINK_MDNS_SERVICE_TYPE, mesh->appname);
432 // Allocate discovery loop object
433 if(!(mesh->catta_poll = catta_simple_poll_new())) {
434 logger(mesh, MESHLINK_ERROR, "Failed to create discovery poll object.\n");
438 // generate some unique host name (we actually do not care about it)
440 generate_rand_string(hostname, sizeof(hostname));
442 // Let's set the host name for this server.
443 CattaServerConfig config;
444 catta_server_config_init(&config);
445 config.host_name = catta_strdup(hostname);
446 config.publish_workstation = 0;
447 config.disallow_other_stacks = 0;
448 config.publish_hinfo = 0;
449 config.publish_addresses = 1;
450 config.publish_no_reverse = 1;
452 /* Allocate a new server */
454 mesh->catta_server = catta_server_new(catta_simple_poll_get(mesh->catta_poll), &config, discovery_server_callback, mesh, &error);
456 /* Free the configuration data */
457 catta_server_config_free(&config);
459 /* Check wether creating the server object succeeded */
460 if(!mesh->catta_server) {
461 logger(mesh, MESHLINK_ERROR, "Failed to create discovery server: %s\n", catta_strerror(error));
465 // Create the service browser
466 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))) {
467 logger(mesh, MESHLINK_ERROR, "Failed to create discovery service browser: %s\n", catta_strerror(catta_server_errno(mesh->catta_server)));
471 // Start the discovery thread
472 if(pthread_create(&mesh->discovery_thread, NULL, discovery_loop, mesh) != 0) {
473 logger(mesh, MESHLINK_ERROR, "Could not start discovery thread: %s\n", strerror(errno));
474 memset(&mesh->discovery_thread, 0, sizeof(mesh)->discovery_thread);
478 mesh->discovery_threadstarted = true;
483 if(mesh->catta_browser != NULL) {
484 catta_s_service_browser_free(mesh->catta_browser);
485 mesh->catta_browser = NULL;
488 if(mesh->catta_server != NULL) {
489 catta_server_free(mesh->catta_server);
490 mesh->catta_server = NULL;
493 if(mesh->catta_poll != NULL) {
494 catta_simple_poll_free(mesh->catta_poll);
495 mesh->catta_poll = NULL;
498 if(mesh->catta_servicetype != NULL) {
499 free(mesh->catta_servicetype);
500 mesh->catta_servicetype = NULL;
506 void discovery_stop(meshlink_handle_t *mesh) {
507 logger(mesh, MESHLINK_DEBUG, "discovery_stop called\n");
510 assert(mesh != NULL);
514 catta_simple_poll_quit(mesh->catta_poll);
516 // Wait for the discovery thread to finish
517 if(mesh->discovery_threadstarted == true) {
518 pthread_join(mesh->discovery_thread, NULL);
519 mesh->discovery_threadstarted = false;
522 // Clean up resources
523 if(mesh->catta_browser != NULL) {
524 catta_s_service_browser_free(mesh->catta_browser);
525 mesh->catta_browser = NULL;
528 if(mesh->catta_group) {
529 catta_s_entry_group_reset(mesh->catta_group);
530 catta_s_entry_group_free(mesh->catta_group);
531 mesh->catta_group = NULL;
534 if(mesh->catta_server != NULL) {
535 catta_server_free(mesh->catta_server);
536 mesh->catta_server = NULL;
539 if(mesh->catta_poll != NULL) {
540 catta_simple_poll_free(mesh->catta_poll);
541 mesh->catta_poll = NULL;
544 if(mesh->catta_servicetype != NULL) {
545 free(mesh->catta_servicetype);
546 mesh->catta_servicetype = NULL;