]> git.meshlink.io Git - meshlink/blob - src/discovery.c
Fix compiling with -Wall -W.
[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         buffer[size - 1] = '\0';
28 }
29
30 static void discovery_entry_group_callback(CattaServer *server, CattaSEntryGroup *group, CattaEntryGroupState state, void *userdata) {
31         (void)server;
32         (void)group;
33         meshlink_handle_t *mesh = userdata;
34
35         // asserts
36         assert(mesh != NULL);
37         assert(mesh->catta_server != NULL);
38         assert(mesh->catta_poll != NULL);
39
40         pthread_mutex_lock(&(mesh->mesh_mutex));
41
42         /* Called whenever the entry group state changes */
43         switch(state) {
44         case CATTA_ENTRY_GROUP_ESTABLISHED:
45                 /* The entry group has been established successfully */
46                 logger(mesh, MESHLINK_DEBUG, "Catta Service successfully established.\n");
47                 break;
48
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?
52                 break;
53
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);
58                 break;
59
60         case CATTA_ENTRY_GROUP_UNCOMMITED:
61         case CATTA_ENTRY_GROUP_REGISTERING:
62                 ;
63         }
64
65         pthread_mutex_unlock(&(mesh->mesh_mutex));
66 }
67
68
69 static void discovery_create_services(meshlink_handle_t *mesh) {
70         char *txt_name = NULL;
71
72         // asserts
73         assert(mesh != 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);
80
81         pthread_mutex_lock(&(mesh->mesh_mutex));
82
83         logger(mesh, MESHLINK_DEBUG, "Adding service\n");
84
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)));
89                         goto fail;
90                 }
91         }
92
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);
96
97         if(txt_name == NULL) {
98                 logger(mesh, MESHLINK_ERROR, "Could not allocate memory for TXT record\n");
99                 goto fail;
100         }
101
102         snprintf(txt_name, txt_name_len, "%s=%s", MESHLINK_MDNS_NAME_KEY, mesh->name);
103
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));
106
107         /* Add the service */
108         int ret = 0;
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));
111                 goto fail;
112         }
113
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));
117                 goto fail;
118         }
119
120         goto done;
121
122 fail:
123         catta_simple_poll_quit(mesh->catta_poll);
124
125 done:
126         if(txt_name)
127                 free(txt_name);
128
129         pthread_mutex_unlock(&(mesh->mesh_mutex));
130 }
131
132 static void discovery_server_callback(CattaServer *server, CattaServerState state, void *userdata) {
133         (void)server;
134         meshlink_handle_t *mesh = userdata;
135
136         // asserts
137         assert(mesh != NULL);
138
139         pthread_mutex_lock(&(mesh->mesh_mutex));
140
141         switch(state) {
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);
147         }
148         break;
149
150         case CATTA_SERVER_COLLISION: {
151                 // asserts
152                 assert(mesh->catta_server != NULL);
153                 assert(mesh->catta_poll != NULL);
154
155                 /* A host name collision happened. Let's pick a new name for the server */
156                 char hostname[17];
157                 generate_rand_string(hostname, sizeof(hostname));
158
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);
161
162                 if(result < 0) {
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);
165                 }
166         }
167         break;
168
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;
176                 }
177         }
178         break;
179
180         case CATTA_SERVER_FAILURE: {
181                 // asserts
182                 assert(mesh->catta_server != NULL);
183                 assert(mesh->catta_poll != NULL);
184
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);
188         }
189         break;
190
191         case CATTA_SERVER_INVALID:
192                 break;
193         }
194
195         pthread_mutex_unlock(&(mesh->mesh_mutex));
196 }
197
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) {
199         (void)interface_;
200         (void)protocol;
201         meshlink_handle_t *mesh = userdata;
202
203         // asserts
204         assert(resolver != NULL);
205         assert(mesh != NULL);
206         assert(mesh->catta_server != NULL);
207
208         pthread_mutex_lock(&(mesh->mesh_mutex));
209
210         /* Called whenever a service has been resolved successfully or timed out */
211         switch(event) {
212         case CATTA_RESOLVER_FAILURE: {
213                 // asserts
214                 assert(name != NULL);
215                 assert(type != NULL);
216                 assert(domain != NULL);
217
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)));
219         }
220         break;
221
222         case CATTA_RESOLVER_FOUND: {
223                 // asserts
224                 assert(name != NULL);
225                 assert(type != NULL);
226                 assert(domain != NULL);
227                 assert(host_name != NULL);
228                 assert(address != NULL);
229                 assert(txt != NULL);
230
231                 char straddr[CATTA_ADDRESS_STR_MAX], *strtxt;
232
233                 logger(mesh, MESHLINK_DEBUG, "(Resolver) Service '%s' of type '%s' in domain '%s':\n", name, type, domain);
234
235                 catta_address_snprint(straddr, sizeof(straddr), address);
236                 strtxt = catta_string_list_to_string(txt);
237                 logger(mesh, MESHLINK_DEBUG,
238                        "\t%s:%u (%s)\n"
239                        "\tTXT=%s\n"
240                        "\tcookie is %u\n"
241                        "\tis_local: %i\n"
242                        "\twide_area: %i\n"
243                        "\tmulticast: %i\n"
244                        "\tcached: %i\n",
245                        host_name, port, straddr,
246                        strtxt,
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));
252                 catta_free(strtxt);
253
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);
257
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);
261
262                         if(node_name[0] == '=' && node_fp[0] == '=') {
263                                 node_name += 1;
264                                 node_fp += 1;
265
266                                 meshlink_node_t *node = meshlink_get_node(mesh, node_name);
267
268                                 if(node != NULL) {
269                                         logger(mesh, MESHLINK_INFO, "Node %s is part of the mesh network.\n", node->name);
270
271                                         sockaddr_t naddress;
272                                         memset(&naddress, 0, sizeof(naddress));
273
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;
279                                         }
280                                         break;
281
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));
286                                         }
287                                         break;
288
289                                         default:
290                                                 naddress.unknown.family = AF_UNKNOWN;
291                                                 break;
292                                         }
293
294                                         if(naddress.unknown.family != AF_UNKNOWN)
295                                                 meshlink_hint_address(mesh, (meshlink_node_t *)node, (struct sockaddr *)&naddress);
296                                         else
297                                                 logger(mesh, MESHLINK_WARNING, "Could not resolve node %s to a known address family type.\n", node->name);
298                                 } else
299                                         logger(mesh, MESHLINK_WARNING, "Node %s is not part of the mesh network.\n", node_name);
300                         } else
301                                 logger(mesh, MESHLINK_WARNING, "TXT records invalid.\n");
302                 } else
303                         logger(mesh, MESHLINK_WARNING, "TXT records missing.\n");
304         }
305         break;
306         }
307
308         catta_s_service_resolver_free(resolver);
309
310         pthread_mutex_unlock(&(mesh->mesh_mutex));
311 }
312
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) {
314         (void)browser;
315         (void)flags;
316         meshlink_handle_t *mesh = userdata;
317
318         // asserts
319         assert(mesh != NULL);
320         assert(mesh->catta_server != NULL);
321         assert(mesh->catta_poll != NULL);
322
323         pthread_mutex_lock(&(mesh->mesh_mutex));
324
325         /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
326         switch(event) {
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);
330         }
331         break;
332
333         case CATTA_BROWSER_NEW: {
334                 // asserts
335                 assert(name != NULL);
336                 assert(type != NULL);
337                 assert(domain != NULL);
338
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)));
346         }
347         break;
348
349         case CATTA_BROWSER_REMOVE: {
350                 // asserts
351                 assert(name != NULL);
352                 assert(type != NULL);
353                 assert(domain != NULL);
354
355                 logger(mesh, MESHLINK_DEBUG, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
356         }
357         break;
358
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");
362         }
363         break;
364         }
365
366         pthread_mutex_unlock(&(mesh->mesh_mutex));
367 }
368
369 static void *discovery_loop(void *userdata) {
370         meshlink_handle_t *mesh = userdata;
371
372         // asserts
373         assert(mesh != NULL);
374         assert(mesh->catta_poll != NULL);
375
376         catta_simple_poll_loop(mesh->catta_poll);
377
378         return NULL;
379 }
380
381 static void discovery_log_cb(CattaLogLevel level, const char *txt) {
382         meshlink_log_level_t mlevel = MESHLINK_CRITICAL;
383
384         switch(level) {
385         case CATTA_LOG_ERROR:
386                 mlevel = MESHLINK_ERROR;
387                 break;
388
389         case CATTA_LOG_WARN:
390                 mlevel = MESHLINK_WARNING;
391                 break;
392
393         case CATTA_LOG_NOTICE:
394         case CATTA_LOG_INFO:
395                 mlevel = MESHLINK_INFO;
396                 break;
397
398         case CATTA_LOG_DEBUG:
399         default:
400                 mlevel = MESHLINK_DEBUG;
401                 break;
402         }
403
404         logger(NULL, mlevel, "%s\n", txt);
405 }
406
407 bool discovery_start(meshlink_handle_t *mesh) {
408         logger(mesh, MESHLINK_DEBUG, "discovery_start called\n");
409
410         // asserts
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);
417
418         // handle catta logs
419         catta_set_log_function(discovery_log_cb);
420
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);
424
425         if(mesh->catta_servicetype == NULL) {
426                 logger(mesh, MESHLINK_ERROR, "Failed to allocate memory for service type string.\n");
427                 goto fail;
428         }
429
430         snprintf(mesh->catta_servicetype, servicetype_strlen, MESHLINK_MDNS_SERVICE_TYPE, mesh->appname);
431
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");
435                 goto fail;
436         }
437
438         // generate some unique host name (we actually do not care about it)
439         char hostname[17];
440         generate_rand_string(hostname, sizeof(hostname));
441
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;
451
452         /* Allocate a new server */
453         int error;
454         mesh->catta_server = catta_server_new(catta_simple_poll_get(mesh->catta_poll), &config, discovery_server_callback, mesh, &error);
455
456         /* Free the configuration data */
457         catta_server_config_free(&config);
458
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));
462                 goto fail;
463         }
464
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)));
468                 goto fail;
469         }
470
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);
475                 goto fail;
476         }
477
478         mesh->discovery_threadstarted = true;
479
480         return true;
481
482 fail:
483         if(mesh->catta_browser != NULL) {
484                 catta_s_service_browser_free(mesh->catta_browser);
485                 mesh->catta_browser = NULL;
486         }
487
488         if(mesh->catta_server != NULL) {
489                 catta_server_free(mesh->catta_server);
490                 mesh->catta_server = NULL;
491         }
492
493         if(mesh->catta_poll != NULL) {
494                 catta_simple_poll_free(mesh->catta_poll);
495                 mesh->catta_poll = NULL;
496         }
497
498         if(mesh->catta_servicetype != NULL) {
499                 free(mesh->catta_servicetype);
500                 mesh->catta_servicetype = NULL;
501         }
502
503         return false;
504 }
505
506 void discovery_stop(meshlink_handle_t *mesh) {
507         logger(mesh, MESHLINK_DEBUG, "discovery_stop called\n");
508
509         // asserts
510         assert(mesh != NULL);
511
512         // Shut down
513         if(mesh->catta_poll)
514                 catta_simple_poll_quit(mesh->catta_poll);
515
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;
520         }
521
522         // Clean up resources
523         if(mesh->catta_browser != NULL) {
524                 catta_s_service_browser_free(mesh->catta_browser);
525                 mesh->catta_browser = NULL;
526         }
527
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;
532         }
533
534         if(mesh->catta_server != NULL) {
535                 catta_server_free(mesh->catta_server);
536                 mesh->catta_server = NULL;
537         }
538
539         if(mesh->catta_poll != NULL) {
540                 catta_simple_poll_free(mesh->catta_poll);
541                 mesh->catta_poll = NULL;
542         }
543
544         if(mesh->catta_servicetype != NULL) {
545                 free(mesh->catta_servicetype);
546                 mesh->catta_servicetype = NULL;
547         }
548 }