]> git.meshlink.io Git - meshlink/blob - src/discovery.c
Add an astylerc file and reformat all code.
[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         meshlink_handle_t *mesh = userdata;
32
33         // asserts
34         assert(mesh != NULL);
35         assert(mesh->catta_server != NULL);
36         assert(mesh->catta_poll != NULL);
37
38         pthread_mutex_lock(&(mesh->mesh_mutex));
39
40         /* Called whenever the entry group state changes */
41         switch(state) {
42         case CATTA_ENTRY_GROUP_ESTABLISHED:
43                 /* The entry group has been established successfully */
44                 logger(mesh, MESHLINK_DEBUG, "Catta Service successfully established.\n");
45                 break;
46
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?
50                 break;
51
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);
56                 break;
57
58         case CATTA_ENTRY_GROUP_UNCOMMITED:
59         case CATTA_ENTRY_GROUP_REGISTERING:
60                 ;
61         }
62
63         pthread_mutex_unlock(&(mesh->mesh_mutex));
64 }
65
66
67 static void discovery_create_services(meshlink_handle_t *mesh) {
68         char *txt_name = NULL;
69
70         // asserts
71         assert(mesh != 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);
78
79         pthread_mutex_lock(&(mesh->mesh_mutex));
80
81         logger(mesh, MESHLINK_DEBUG, "Adding service\n");
82
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)));
87                         goto fail;
88                 }
89         }
90
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);
94
95         if(txt_name == NULL) {
96                 logger(mesh, MESHLINK_ERROR, "Could not allocate memory for TXT record\n");
97                 goto fail;
98         }
99
100         snprintf(txt_name, txt_name_len, "%s=%s", MESHLINK_MDNS_NAME_KEY, mesh->name);
101
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));
104
105         /* Add the service */
106         int ret = 0;
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));
109                 goto fail;
110         }
111
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));
115                 goto fail;
116         }
117
118         goto done;
119
120 fail:
121         catta_simple_poll_quit(mesh->catta_poll);
122
123 done:
124         if(txt_name)
125                 free(txt_name);
126
127         pthread_mutex_unlock(&(mesh->mesh_mutex));
128 }
129
130 static void discovery_server_callback(CattaServer *server, CattaServerState state, void * userdata) {
131         meshlink_handle_t *mesh = userdata;
132
133         // asserts
134         assert(mesh != NULL);
135
136         pthread_mutex_lock(&(mesh->mesh_mutex));
137
138         switch(state) {
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);
144         }
145         break;
146
147         case CATTA_SERVER_COLLISION: {
148                 // asserts
149                 assert(mesh->catta_server != NULL);
150                 assert(mesh->catta_poll != NULL);
151
152                 /* A host name collision happened. Let's pick a new name for the server */
153                 char hostname[17];
154                 generate_rand_string(hostname, sizeof(hostname));
155
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);
158
159                 if(result < 0) {
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);
162                 }
163         }
164         break;
165
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;
173                 }
174         }
175         break;
176
177         case CATTA_SERVER_FAILURE: {
178                 // asserts
179                 assert(mesh->catta_server != NULL);
180                 assert(mesh->catta_poll != NULL);
181
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);
185         }
186         break;
187
188         case CATTA_SERVER_INVALID:
189                 break;
190         }
191
192         pthread_mutex_unlock(&(mesh->mesh_mutex));
193 }
194
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;
197
198         // asserts
199         assert(resolver != NULL);
200         assert(mesh != NULL);
201         assert(mesh->catta_server != NULL);
202
203         pthread_mutex_lock(&(mesh->mesh_mutex));
204
205         /* Called whenever a service has been resolved successfully or timed out */
206         switch(event) {
207         case CATTA_RESOLVER_FAILURE: {
208                 // asserts
209                 assert(name != NULL);
210                 assert(type != NULL);
211                 assert(domain != NULL);
212
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)));
214         }
215         break;
216
217         case CATTA_RESOLVER_FOUND: {
218                 // asserts
219                 assert(name != NULL);
220                 assert(type != NULL);
221                 assert(domain != NULL);
222                 assert(host_name != NULL);
223                 assert(address != NULL);
224                 assert(txt != NULL);
225
226                 char straddr[CATTA_ADDRESS_STR_MAX], *strtxt;
227
228                 logger(mesh, MESHLINK_DEBUG, "(Resolver) Service '%s' of type '%s' in domain '%s':\n", name, type, domain);
229
230                 catta_address_snprint(straddr, sizeof(straddr), address);
231                 strtxt = catta_string_list_to_string(txt);
232                 logger(mesh, MESHLINK_DEBUG,
233                        "\t%s:%u (%s)\n"
234                        "\tTXT=%s\n"
235                        "\tcookie is %u\n"
236                        "\tis_local: %i\n"
237                        "\twide_area: %i\n"
238                        "\tmulticast: %i\n"
239                        "\tcached: %i\n",
240                        host_name, port, straddr,
241                        strtxt,
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));
247                 catta_free(strtxt);
248
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);
252
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);
256
257                         if(node_name[0] == '=' && node_fp[0] == '=') {
258                                 node_name += 1;
259                                 node_fp += 1;
260
261                                 meshlink_node_t *node = meshlink_get_node(mesh, node_name);
262
263                                 if(node != NULL) {
264                                         logger(mesh, MESHLINK_INFO, "Node %s is part of the mesh network.\n", node->name);
265
266                                         sockaddr_t naddress;
267                                         memset(&naddress, 0, sizeof(naddress));
268
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;
274                                         }
275                                         break;
276
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));
281                                         }
282                                         break;
283
284                                         default:
285                                                 naddress.unknown.family = AF_UNKNOWN;
286                                                 break;
287                                         }
288
289                                         if(naddress.unknown.family != AF_UNKNOWN)
290                                                 meshlink_hint_address(mesh, (meshlink_node_t *)node, (struct sockaddr*)&naddress);
291                                         else
292                                                 logger(mesh, MESHLINK_WARNING, "Could not resolve node %s to a known address family type.\n", node->name);
293                                 } else
294                                         logger(mesh, MESHLINK_WARNING, "Node %s is not part of the mesh network.\n", node_name);
295                         } else
296                                 logger(mesh, MESHLINK_WARNING, "TXT records invalid.\n");
297                 } else
298                         logger(mesh, MESHLINK_WARNING, "TXT records missing.\n");
299         }
300         break;
301         }
302
303         catta_s_service_resolver_free(resolver);
304
305         pthread_mutex_unlock(&(mesh->mesh_mutex));
306 }
307
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;
310
311         // asserts
312         assert(mesh != NULL);
313         assert(mesh->catta_server != NULL);
314         assert(mesh->catta_poll != NULL);
315
316         pthread_mutex_lock(&(mesh->mesh_mutex));
317
318         /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
319         switch(event) {
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);
323         }
324         break;
325
326         case CATTA_BROWSER_NEW: {
327                 // asserts
328                 assert(name != NULL);
329                 assert(type != NULL);
330                 assert(domain != NULL);
331
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)));
339         }
340         break;
341
342         case CATTA_BROWSER_REMOVE: {
343                 // asserts
344                 assert(name != NULL);
345                 assert(type != NULL);
346                 assert(domain != NULL);
347
348                 logger(mesh, MESHLINK_DEBUG, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
349         }
350         break;
351
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");
355         }
356         break;
357         }
358
359         pthread_mutex_unlock(&(mesh->mesh_mutex));
360 }
361
362 static void *discovery_loop(void *userdata) {
363         meshlink_handle_t *mesh = userdata;
364
365         // asserts
366         assert(mesh != NULL);
367         assert(mesh->catta_poll != NULL);
368
369         catta_simple_poll_loop(mesh->catta_poll);
370
371         return NULL;
372 }
373
374 static void discovery_log_cb(CattaLogLevel level, const char *txt) {
375         meshlink_log_level_t mlevel = MESHLINK_CRITICAL;
376
377         switch(level) {
378         case CATTA_LOG_ERROR:
379                 mlevel = MESHLINK_ERROR;
380                 break;
381
382         case CATTA_LOG_WARN:
383                 mlevel = MESHLINK_WARNING;
384                 break;
385
386         case CATTA_LOG_NOTICE:
387         case CATTA_LOG_INFO:
388                 mlevel = MESHLINK_INFO;
389                 break;
390
391         case CATTA_LOG_DEBUG:
392                 mlevel = MESHLINK_DEBUG;
393                 break;
394         }
395
396         logger(NULL, mlevel, "%s\n", txt);
397 }
398
399 bool discovery_start(meshlink_handle_t *mesh) {
400         logger(mesh, MESHLINK_DEBUG, "discovery_start called\n");
401
402         // asserts
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);
409
410         // handle catta logs
411         catta_set_log_function(discovery_log_cb);
412
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);
416
417         if(mesh->catta_servicetype == NULL) {
418                 logger(mesh, MESHLINK_ERROR, "Failed to allocate memory for service type string.\n");
419                 goto fail;
420         }
421
422         snprintf(mesh->catta_servicetype, servicetype_strlen, MESHLINK_MDNS_SERVICE_TYPE, mesh->appname);
423
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");
427                 goto fail;
428         }
429
430         // generate some unique host name (we actually do not care about it)
431         char hostname[17];
432         generate_rand_string(hostname, sizeof(hostname));
433
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;
443
444         /* Allocate a new server */
445         int error;
446         mesh->catta_server = catta_server_new(catta_simple_poll_get(mesh->catta_poll), &config, discovery_server_callback, mesh, &error);
447
448         /* Free the configuration data */
449         catta_server_config_free(&config);
450
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));
454                 goto fail;
455         }
456
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)));
460                 goto fail;
461         }
462
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);
467                 goto fail;
468         }
469
470         mesh->discovery_threadstarted = true;
471
472         return true;
473
474 fail:
475         if(mesh->catta_browser != NULL) {
476                 catta_s_service_browser_free(mesh->catta_browser);
477                 mesh->catta_browser = NULL;
478         }
479
480         if(mesh->catta_server != NULL) {
481                 catta_server_free(mesh->catta_server);
482                 mesh->catta_server = NULL;
483         }
484
485         if(mesh->catta_poll != NULL) {
486                 catta_simple_poll_free(mesh->catta_poll);
487                 mesh->catta_poll = NULL;
488         }
489
490         if(mesh->catta_servicetype != NULL) {
491                 free(mesh->catta_servicetype);
492                 mesh->catta_servicetype = NULL;
493         }
494
495         return false;
496 }
497
498 void discovery_stop(meshlink_handle_t *mesh) {
499         logger(mesh, MESHLINK_DEBUG, "discovery_stop called\n");
500
501         // asserts
502         assert(mesh != NULL);
503
504         // Shut down
505         if(mesh->catta_poll)
506                 catta_simple_poll_quit(mesh->catta_poll);
507
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;
512         }
513
514         // Clean up resources
515         if(mesh->catta_browser != NULL) {
516                 catta_s_service_browser_free(mesh->catta_browser);
517                 mesh->catta_browser = NULL;
518         }
519
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;
524         }
525
526         if(mesh->catta_server != NULL) {
527                 catta_server_free(mesh->catta_server);
528                 mesh->catta_server = NULL;
529         }
530
531         if(mesh->catta_poll != NULL) {
532                 catta_simple_poll_free(mesh->catta_poll);
533                 mesh->catta_poll = NULL;
534         }
535
536         if(mesh->catta_servicetype != NULL) {
537                 free(mesh->catta_servicetype);
538                 mesh->catta_servicetype = NULL;
539         }
540 }