]> git.meshlink.io Git - meshlink/blob - src/discovery.c
c74c71c2f9874ea51140356b9397246f3ab180d2
[meshlink] / src / discovery.c
1 #include <catta/core.h>
2 #include <catta/lookup.h>
3 #include <catta/publish.h>
4 #include <catta/log.h>
5 #include <catta/simple-watch.h>
6 #include <catta/malloc.h>
7 #include <catta/alternative.h>
8 #include <catta/error.h>
9
10 #include "meshlink_internal.h"
11 #include "discovery.h"
12 #include "sockaddr.h"
13 #include "logger.h"
14
15 #include <pthread.h>
16
17 #include <netinet/in.h>
18
19 #define MESHLINK_MDNS_SERVICE_TYPE "_%s._tcp"
20 #define MESHLINK_MDNS_NAME_KEY "name"
21 #define MESHLINK_MDNS_FINGERPRINT_KEY "fingerprint"
22
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));
26         }
27
28         buffer[size - 1] = '\0';
29 }
30
31 static void discovery_entry_group_callback(CattaServer *server, CattaSEntryGroup *group, CattaEntryGroupState state, void *userdata) {
32         (void)server;
33         (void)group;
34         meshlink_handle_t *mesh = userdata;
35
36         // asserts
37         assert(mesh != NULL);
38         assert(mesh->catta_server != NULL);
39         assert(mesh->catta_poll != NULL);
40
41         pthread_mutex_lock(&(mesh->mesh_mutex));
42
43         /* Called whenever the entry group state changes */
44         switch(state) {
45         case CATTA_ENTRY_GROUP_ESTABLISHED:
46                 /* The entry group has been established successfully */
47                 logger(mesh, MESHLINK_DEBUG, "Catta Service successfully established.\n");
48                 break;
49
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?
53                 break;
54
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);
59                 break;
60
61         case CATTA_ENTRY_GROUP_UNCOMMITED:
62         case CATTA_ENTRY_GROUP_REGISTERING:
63                 ;
64         }
65
66         pthread_mutex_unlock(&(mesh->mesh_mutex));
67 }
68
69
70 static void discovery_create_services(meshlink_handle_t *mesh) {
71         char *txt_name = NULL;
72
73         // asserts
74         assert(mesh != 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);
81
82         pthread_mutex_lock(&(mesh->mesh_mutex));
83
84         logger(mesh, MESHLINK_DEBUG, "Adding service\n");
85
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)));
90                         goto fail;
91                 }
92         }
93
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);
97
98         if(txt_name == NULL) {
99                 logger(mesh, MESHLINK_ERROR, "Could not allocate memory for TXT record\n");
100                 goto fail;
101         }
102
103         snprintf(txt_name, txt_name_len, "%s=%s", MESHLINK_MDNS_NAME_KEY, mesh->name);
104
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));
107
108         /* Add the service */
109         int ret = 0;
110
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));
113                 goto fail;
114         }
115
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));
119                 goto fail;
120         }
121
122         goto done;
123
124 fail:
125         catta_simple_poll_quit(mesh->catta_poll);
126
127 done:
128
129         if(txt_name) {
130                 free(txt_name);
131         }
132
133         pthread_mutex_unlock(&(mesh->mesh_mutex));
134 }
135
136 static void discovery_server_callback(CattaServer *server, CattaServerState state, void *userdata) {
137         (void)server;
138         meshlink_handle_t *mesh = userdata;
139
140         // asserts
141         assert(mesh != NULL);
142
143         pthread_mutex_lock(&(mesh->mesh_mutex));
144
145         switch(state) {
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);
151                 }
152         }
153         break;
154
155         case CATTA_SERVER_COLLISION: {
156                 // asserts
157                 assert(mesh->catta_server != NULL);
158                 assert(mesh->catta_poll != NULL);
159
160                 /* A host name collision happened. Let's pick a new name for the server */
161                 char hostname[17];
162                 generate_rand_string(hostname, sizeof(hostname));
163
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);
166
167                 if(result < 0) {
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);
170                 }
171         }
172         break;
173
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;
181                 }
182         }
183         break;
184
185         case CATTA_SERVER_FAILURE: {
186                 // asserts
187                 assert(mesh->catta_server != NULL);
188                 assert(mesh->catta_poll != NULL);
189
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);
193         }
194         break;
195
196         case CATTA_SERVER_INVALID:
197                 break;
198         }
199
200         pthread_mutex_unlock(&(mesh->mesh_mutex));
201 }
202
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) {
204         (void)interface_;
205         (void)protocol;
206         meshlink_handle_t *mesh = userdata;
207
208         // asserts
209         assert(resolver != NULL);
210         assert(mesh != NULL);
211         assert(mesh->catta_server != NULL);
212
213         pthread_mutex_lock(&(mesh->mesh_mutex));
214
215         /* Called whenever a service has been resolved successfully or timed out */
216         switch(event) {
217         case CATTA_RESOLVER_FAILURE: {
218                 // asserts
219                 assert(name != NULL);
220                 assert(type != NULL);
221                 assert(domain != NULL);
222
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)));
224         }
225         break;
226
227         case CATTA_RESOLVER_FOUND: {
228                 // asserts
229                 assert(name != NULL);
230                 assert(type != NULL);
231                 assert(domain != NULL);
232                 assert(host_name != NULL);
233                 assert(address != NULL);
234                 assert(txt != NULL);
235
236                 char straddr[CATTA_ADDRESS_STR_MAX], *strtxt;
237
238                 logger(mesh, MESHLINK_DEBUG, "(Resolver) Service '%s' of type '%s' in domain '%s':\n", name, type, domain);
239
240                 catta_address_snprint(straddr, sizeof(straddr), address);
241                 strtxt = catta_string_list_to_string(txt);
242                 logger(mesh, MESHLINK_DEBUG,
243                        "\t%s:%u (%s)\n"
244                        "\tTXT=%s\n"
245                        "\tcookie is %u\n"
246                        "\tis_local: %i\n"
247                        "\twide_area: %i\n"
248                        "\tmulticast: %i\n"
249                        "\tcached: %i\n",
250                        host_name, port, straddr,
251                        strtxt,
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));
257                 catta_free(strtxt);
258
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);
262
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);
266
267                         if(node_name[0] == '=' && node_fp[0] == '=') {
268                                 node_name += 1;
269                                 node_fp += 1;
270
271                                 meshlink_node_t *node = meshlink_get_node(mesh, node_name);
272
273                                 if(node != NULL) {
274                                         logger(mesh, MESHLINK_INFO, "Node %s is part of the mesh network.\n", node->name);
275
276                                         sockaddr_t naddress;
277                                         memset(&naddress, 0, sizeof(naddress));
278
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;
284                                         }
285                                         break;
286
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));
291                                         }
292                                         break;
293
294                                         default:
295                                                 naddress.unknown.family = AF_UNKNOWN;
296                                                 break;
297                                         }
298
299                                         if(naddress.unknown.family != AF_UNKNOWN) {
300                                                 meshlink_hint_address(mesh, (meshlink_node_t *)node, (struct sockaddr *)&naddress);
301                                         } else {
302                                                 logger(mesh, MESHLINK_WARNING, "Could not resolve node %s to a known address family type.\n", node->name);
303                                         }
304                                 } else {
305                                         logger(mesh, MESHLINK_WARNING, "Node %s is not part of the mesh network.\n", node_name);
306                                 }
307                         } else {
308                                 logger(mesh, MESHLINK_WARNING, "TXT records invalid.\n");
309                         }
310                 } else {
311                         logger(mesh, MESHLINK_WARNING, "TXT records missing.\n");
312                 }
313         }
314         break;
315         }
316
317         catta_s_service_resolver_free(resolver);
318
319         pthread_mutex_unlock(&(mesh->mesh_mutex));
320 }
321
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) {
323         (void)browser;
324         (void)flags;
325         meshlink_handle_t *mesh = userdata;
326
327         // asserts
328         assert(mesh != NULL);
329         assert(mesh->catta_server != NULL);
330         assert(mesh->catta_poll != NULL);
331
332         pthread_mutex_lock(&(mesh->mesh_mutex));
333
334         /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
335         switch(event) {
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);
339         }
340         break;
341
342         case CATTA_BROWSER_NEW: {
343                 // asserts
344                 assert(name != NULL);
345                 assert(type != NULL);
346                 assert(domain != NULL);
347
348                 logger(mesh, MESHLINK_DEBUG, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
349
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)));
356                 }
357         }
358         break;
359
360         case CATTA_BROWSER_REMOVE: {
361                 // asserts
362                 assert(name != NULL);
363                 assert(type != NULL);
364                 assert(domain != NULL);
365
366                 logger(mesh, MESHLINK_DEBUG, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
367         }
368         break;
369
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");
373         }
374         break;
375         }
376
377         pthread_mutex_unlock(&(mesh->mesh_mutex));
378 }
379
380 static void *discovery_loop(void *userdata) {
381         meshlink_handle_t *mesh = userdata;
382
383         // asserts
384         assert(mesh != NULL);
385         assert(mesh->catta_poll != NULL);
386
387         catta_simple_poll_loop(mesh->catta_poll);
388
389         return NULL;
390 }
391
392 static void discovery_log_cb(CattaLogLevel level, const char *txt) {
393         meshlink_log_level_t mlevel = MESHLINK_CRITICAL;
394
395         switch(level) {
396         case CATTA_LOG_ERROR:
397                 mlevel = MESHLINK_ERROR;
398                 break;
399
400         case CATTA_LOG_WARN:
401                 mlevel = MESHLINK_WARNING;
402                 break;
403
404         case CATTA_LOG_NOTICE:
405         case CATTA_LOG_INFO:
406                 mlevel = MESHLINK_INFO;
407                 break;
408
409         case CATTA_LOG_DEBUG:
410         default:
411                 mlevel = MESHLINK_DEBUG;
412                 break;
413         }
414
415         logger(NULL, mlevel, "%s\n", txt);
416 }
417
418 bool discovery_start(meshlink_handle_t *mesh) {
419         logger(mesh, MESHLINK_DEBUG, "discovery_start called\n");
420
421         // asserts
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);
428
429         // handle catta logs
430         catta_set_log_function(discovery_log_cb);
431
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);
435
436         if(mesh->catta_servicetype == NULL) {
437                 logger(mesh, MESHLINK_ERROR, "Failed to allocate memory for service type string.\n");
438                 goto fail;
439         }
440
441         snprintf(mesh->catta_servicetype, servicetype_strlen, MESHLINK_MDNS_SERVICE_TYPE, mesh->appname);
442
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");
446                 goto fail;
447         }
448
449         // generate some unique host name (we actually do not care about it)
450         char hostname[17];
451         generate_rand_string(hostname, sizeof(hostname));
452
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;
462
463         /* Allocate a new server */
464         int error;
465         mesh->catta_server = catta_server_new(catta_simple_poll_get(mesh->catta_poll), &config, discovery_server_callback, mesh, &error);
466
467         /* Free the configuration data */
468         catta_server_config_free(&config);
469
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));
473                 goto fail;
474         }
475
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)));
479                 goto fail;
480         }
481
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);
486                 goto fail;
487         }
488
489         mesh->discovery_threadstarted = true;
490
491         return true;
492
493 fail:
494
495         if(mesh->catta_browser != NULL) {
496                 catta_s_service_browser_free(mesh->catta_browser);
497                 mesh->catta_browser = NULL;
498         }
499
500         if(mesh->catta_server != NULL) {
501                 catta_server_free(mesh->catta_server);
502                 mesh->catta_server = NULL;
503         }
504
505         if(mesh->catta_poll != NULL) {
506                 catta_simple_poll_free(mesh->catta_poll);
507                 mesh->catta_poll = NULL;
508         }
509
510         if(mesh->catta_servicetype != NULL) {
511                 free(mesh->catta_servicetype);
512                 mesh->catta_servicetype = NULL;
513         }
514
515         return false;
516 }
517
518 void discovery_stop(meshlink_handle_t *mesh) {
519         logger(mesh, MESHLINK_DEBUG, "discovery_stop called\n");
520
521         // asserts
522         assert(mesh != NULL);
523
524         // Shut down
525         if(mesh->catta_poll) {
526                 catta_simple_poll_quit(mesh->catta_poll);
527         }
528
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;
533         }
534
535         // Clean up resources
536         if(mesh->catta_browser != NULL) {
537                 catta_s_service_browser_free(mesh->catta_browser);
538                 mesh->catta_browser = NULL;
539         }
540
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;
545         }
546
547         if(mesh->catta_server != NULL) {
548                 catta_server_free(mesh->catta_server);
549                 mesh->catta_server = NULL;
550         }
551
552         if(mesh->catta_poll != NULL) {
553                 catta_simple_poll_free(mesh->catta_poll);
554                 mesh->catta_poll = NULL;
555         }
556
557         if(mesh->catta_servicetype != NULL) {
558                 free(mesh->catta_servicetype);
559                 mesh->catta_servicetype = NULL;
560         }
561 }