]> git.meshlink.io Git - meshlink/blob - src/discovery.c
Fix compiler warnings.
[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         default:
393                 mlevel = MESHLINK_DEBUG;
394                 break;
395         }
396
397         logger(NULL, mlevel, "%s\n", txt);
398 }
399
400 bool discovery_start(meshlink_handle_t *mesh) {
401         logger(mesh, MESHLINK_DEBUG, "discovery_start called\n");
402
403         // asserts
404         assert(mesh != NULL);
405         assert(mesh->catta_poll == NULL);
406         assert(mesh->catta_server == NULL);
407         assert(mesh->catta_browser == NULL);
408         assert(mesh->discovery_threadstarted == false);
409         assert(mesh->catta_servicetype == NULL);
410
411         // handle catta logs
412         catta_set_log_function(discovery_log_cb);
413
414         // create service type string
415         size_t servicetype_strlen = sizeof(MESHLINK_MDNS_SERVICE_TYPE) + strlen(mesh->appname) + 1;
416         mesh->catta_servicetype = malloc(servicetype_strlen);
417
418         if(mesh->catta_servicetype == NULL) {
419                 logger(mesh, MESHLINK_ERROR, "Failed to allocate memory for service type string.\n");
420                 goto fail;
421         }
422
423         snprintf(mesh->catta_servicetype, servicetype_strlen, MESHLINK_MDNS_SERVICE_TYPE, mesh->appname);
424
425         // Allocate discovery loop object
426         if(!(mesh->catta_poll = catta_simple_poll_new())) {
427                 logger(mesh, MESHLINK_ERROR, "Failed to create discovery poll object.\n");
428                 goto fail;
429         }
430
431         // generate some unique host name (we actually do not care about it)
432         char hostname[17];
433         generate_rand_string(hostname, sizeof(hostname));
434
435         // Let's set the host name for this server.
436         CattaServerConfig config;
437         catta_server_config_init(&config);
438         config.host_name = catta_strdup(hostname);
439         config.publish_workstation = 0;
440         config.disallow_other_stacks = 0;
441         config.publish_hinfo = 0;
442         config.publish_addresses = 1;
443         config.publish_no_reverse = 1;
444
445         /* Allocate a new server */
446         int error;
447         mesh->catta_server = catta_server_new(catta_simple_poll_get(mesh->catta_poll), &config, discovery_server_callback, mesh, &error);
448
449         /* Free the configuration data */
450         catta_server_config_free(&config);
451
452         /* Check wether creating the server object succeeded */
453         if(!mesh->catta_server) {
454                 logger(mesh, MESHLINK_ERROR, "Failed to create discovery server: %s\n", catta_strerror(error));
455                 goto fail;
456         }
457
458         // Create the service browser
459         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))) {
460                 logger(mesh, MESHLINK_ERROR, "Failed to create discovery service browser: %s\n", catta_strerror(catta_server_errno(mesh->catta_server)));
461                 goto fail;
462         }
463
464         // Start the discovery thread
465         if(pthread_create(&mesh->discovery_thread, NULL, discovery_loop, mesh) != 0) {
466                 logger(mesh, MESHLINK_ERROR, "Could not start discovery thread: %s\n", strerror(errno));
467                 memset(&mesh->discovery_thread, 0, sizeof(mesh)->discovery_thread);
468                 goto fail;
469         }
470
471         mesh->discovery_threadstarted = true;
472
473         return true;
474
475 fail:
476         if(mesh->catta_browser != NULL) {
477                 catta_s_service_browser_free(mesh->catta_browser);
478                 mesh->catta_browser = NULL;
479         }
480
481         if(mesh->catta_server != NULL) {
482                 catta_server_free(mesh->catta_server);
483                 mesh->catta_server = NULL;
484         }
485
486         if(mesh->catta_poll != NULL) {
487                 catta_simple_poll_free(mesh->catta_poll);
488                 mesh->catta_poll = NULL;
489         }
490
491         if(mesh->catta_servicetype != NULL) {
492                 free(mesh->catta_servicetype);
493                 mesh->catta_servicetype = NULL;
494         }
495
496         return false;
497 }
498
499 void discovery_stop(meshlink_handle_t *mesh) {
500         logger(mesh, MESHLINK_DEBUG, "discovery_stop called\n");
501
502         // asserts
503         assert(mesh != NULL);
504
505         // Shut down
506         if(mesh->catta_poll)
507                 catta_simple_poll_quit(mesh->catta_poll);
508
509         // Wait for the discovery thread to finish
510         if(mesh->discovery_threadstarted == true) {
511                 pthread_join(mesh->discovery_thread, NULL);
512                 mesh->discovery_threadstarted = false;
513         }
514
515         // Clean up resources
516         if(mesh->catta_browser != NULL) {
517                 catta_s_service_browser_free(mesh->catta_browser);
518                 mesh->catta_browser = NULL;
519         }
520
521         if(mesh->catta_group) {
522                 catta_s_entry_group_reset(mesh->catta_group);
523                 catta_s_entry_group_free(mesh->catta_group);
524                 mesh->catta_group = NULL;
525         }
526
527         if(mesh->catta_server != NULL) {
528                 catta_server_free(mesh->catta_server);
529                 mesh->catta_server = NULL;
530         }
531
532         if(mesh->catta_poll != NULL) {
533                 catta_simple_poll_free(mesh->catta_poll);
534                 mesh->catta_poll = NULL;
535         }
536
537         if(mesh->catta_servicetype != NULL) {
538                 free(mesh->catta_servicetype);
539                 mesh->catta_servicetype = NULL;
540         }
541 }