]> git.meshlink.io Git - meshlink/blob - src/discovery.c
67a349499c9c4a0162a5252aa38797f0e4775602
[meshlink] / src / discovery.c
1 #include "system.h"
2
3 #include <catta/core.h>
4 #include <catta/lookup.h>
5 #include <catta/publish.h>
6 #include <catta/log.h>
7 #include <catta/simple-watch.h>
8 #include <catta/malloc.h>
9 #include <catta/alternative.h>
10 #include <catta/error.h>
11
12 #include "meshlink_internal.h"
13 #include "discovery.h"
14 #include "sockaddr.h"
15 #include "logger.h"
16 #include "node.h"
17 #include "connection.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 *fingerprint = NULL;
72         char *txt_name = NULL;
73         char *txt_fingerprint = NULL;
74
75         // asserts
76         assert(mesh != NULL);
77         assert(mesh->name != NULL);
78         assert(mesh->myport != NULL);
79         assert(mesh->catta_server != NULL);
80         assert(mesh->catta_poll != NULL);
81         assert(mesh->catta_servicetype != NULL);
82         assert(mesh->self != NULL);
83
84         pthread_mutex_lock(&(mesh->mesh_mutex));
85
86         logger(mesh, MESHLINK_DEBUG, "Adding service\n");
87
88         /* Ifthis is the first time we're called, let's create a new entry group */
89         if(!mesh->catta_group) {
90                 if(!(mesh->catta_group = catta_s_entry_group_new(mesh->catta_server, discovery_entry_group_callback, mesh))) {
91                         logger(mesh, MESHLINK_ERROR, "catta_entry_group_new() failed: %s\n", catta_strerror(catta_server_errno(mesh->catta_server)));
92                         goto fail;
93                 }
94         }
95
96         /* Create txt records */
97         fingerprint = meshlink_get_fingerprint(mesh, (meshlink_node_t *)mesh->self);
98         xasprintf(&txt_name, "%s=%s", MESHLINK_MDNS_NAME_KEY, mesh->name);
99         xasprintf(&txt_fingerprint, "%s=%s", MESHLINK_MDNS_FINGERPRINT_KEY, fingerprint);
100
101         /* Add the service */
102         int ret = 0;
103
104         if((ret = catta_server_add_service(mesh->catta_server, mesh->catta_group, CATTA_IF_UNSPEC, CATTA_PROTO_UNSPEC, 0, fingerprint, mesh->catta_servicetype, NULL, NULL, atoi(mesh->myport), txt_name, txt_fingerprint, NULL)) < 0) {
105                 logger(mesh, MESHLINK_ERROR, "Failed to add service: %s\n", catta_strerror(ret));
106                 goto fail;
107         }
108
109         /* Tell the server to register the service */
110         if((ret = catta_s_entry_group_commit(mesh->catta_group)) < 0) {
111                 logger(mesh, MESHLINK_ERROR, "Failed to commit entry_group: %s\n", catta_strerror(ret));
112                 goto fail;
113         }
114
115         goto done;
116
117 fail:
118         catta_simple_poll_quit(mesh->catta_poll);
119
120 done:
121         free(fingerprint);
122         free(txt_name);
123         free(txt_fingerprint);
124
125         pthread_mutex_unlock(&(mesh->mesh_mutex));
126 }
127
128 static void discovery_server_callback(CattaServer *server, CattaServerState state, void *userdata) {
129         (void)server;
130         meshlink_handle_t *mesh = userdata;
131
132         // asserts
133         assert(mesh != NULL);
134
135         switch(state) {
136         case CATTA_SERVER_RUNNING:
137
138                 /* The serve has startup successfully and registered its host
139                  * name on the network, so it's time to create our services */
140                 if(!mesh->catta_group) {
141                         discovery_create_services(mesh);
142                 }
143
144                 break;
145
146         case CATTA_SERVER_COLLISION: {
147                 // asserts
148                 assert(mesh->catta_server != NULL);
149                 assert(mesh->catta_poll != NULL);
150
151                 /* A host name collision happened. Let's pick a new name for the server */
152                 char hostname[17];
153                 generate_rand_string(hostname, sizeof(hostname));
154                 int result = catta_server_set_host_name(mesh->catta_server, hostname);
155
156                 if(result < 0) {
157                         catta_simple_poll_quit(mesh->catta_poll);
158                 }
159         }
160         break;
161
162         case CATTA_SERVER_REGISTERING:
163
164                 /* Let's drop our registered services. When the server is back
165                  * in CATTA_SERVER_RUNNING state we will register them
166                  * again with the new host name. */
167                 if(mesh->catta_group) {
168                         catta_s_entry_group_reset(mesh->catta_group);
169                         mesh->catta_group = NULL;
170                 }
171
172                 break;
173
174         case CATTA_SERVER_FAILURE:
175                 // asserts
176                 assert(mesh->catta_server != NULL);
177                 assert(mesh->catta_poll != NULL);
178
179                 /* Terminate on failure */
180                 catta_simple_poll_quit(mesh->catta_poll);
181                 break;
182
183         case CATTA_SERVER_INVALID:
184                 break;
185         }
186 }
187
188 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) {
189         (void)interface_;
190         (void)protocol;
191         (void)flags;
192         meshlink_handle_t *mesh = userdata;
193
194         // asserts
195         assert(resolver != NULL);
196         assert(mesh != NULL);
197         assert(mesh->catta_server != NULL);
198
199         /* Called whenever a service has been resolved successfully or timed out */
200         switch(event) {
201         case CATTA_RESOLVER_FAILURE:
202                 // asserts
203                 assert(name != NULL);
204                 assert(type != NULL);
205                 assert(domain != NULL);
206                 break;
207
208         case CATTA_RESOLVER_FOUND: {
209                 // asserts
210                 assert(name != NULL);
211                 assert(type != NULL);
212                 assert(domain != NULL);
213                 assert(host_name != NULL);
214                 assert(address != NULL);
215                 assert(txt != NULL);
216
217                 // retrieve fingerprint
218                 CattaStringList *node_name_li = catta_string_list_find(txt, MESHLINK_MDNS_NAME_KEY);
219                 CattaStringList *node_fp_li = catta_string_list_find(txt, MESHLINK_MDNS_FINGERPRINT_KEY);
220
221                 if(node_name_li != NULL && node_fp_li != NULL) {
222                         char *node_name = (char *)catta_string_list_get_text(node_name_li) + strlen(MESHLINK_MDNS_NAME_KEY);
223                         char *node_fp = (char *)catta_string_list_get_text(node_fp_li) + strlen(MESHLINK_MDNS_FINGERPRINT_KEY);
224
225                         if(node_name[0] == '=' && node_fp[0] == '=') {
226                                 pthread_mutex_lock(&(mesh->mesh_mutex));
227
228                                 node_name += 1;
229                                 node_fp += 1;
230
231                                 meshlink_node_t *node = meshlink_get_node(mesh, node_name);
232
233                                 if(node != NULL) {
234                                         logger(mesh, MESHLINK_INFO, "Node %s is part of the mesh network.\n", node->name);
235
236                                         sockaddr_t naddress;
237                                         memset(&naddress, 0, sizeof(naddress));
238
239                                         switch(address->proto) {
240                                         case CATTA_PROTO_INET: {
241                                                 naddress.in.sin_family = AF_INET;
242                                                 naddress.in.sin_port = htons(port);
243                                                 naddress.in.sin_addr.s_addr = address->data.ipv4.address;
244                                         }
245                                         break;
246
247                                         case CATTA_PROTO_INET6: {
248                                                 naddress.in6.sin6_family = AF_INET6;
249                                                 naddress.in6.sin6_port = htons(port);
250                                                 memcpy(naddress.in6.sin6_addr.s6_addr, address->data.ipv6.address, sizeof(naddress.in6.sin6_addr.s6_addr));
251                                         }
252                                         break;
253
254                                         default:
255                                                 naddress.unknown.family = AF_UNKNOWN;
256                                                 break;
257                                         }
258
259                                         if(naddress.unknown.family != AF_UNKNOWN) {
260                                                 meshlink_hint_address(mesh, (meshlink_node_t *)node, (struct sockaddr *)&naddress);
261                                                 pthread_mutex_lock(&(mesh->mesh_mutex));
262
263                                                 node_t *n = (node_t *)node;
264
265                                                 if(n->connection && n->connection->outgoing) {
266                                                         n->connection->outgoing->timeout = 0;
267
268                                                         if(n->connection->outgoing->ev.cb) {
269                                                                 timeout_set(&mesh->loop, &n->connection->outgoing->ev, &(struct timeval) {
270                                                                         0, 0
271                                                                 });
272                                                         }
273
274                                                         n->connection->last_ping_time = 0;
275                                                 }
276
277                                                 pthread_mutex_unlock(&(mesh->mesh_mutex));
278                                         } else {
279                                                 logger(mesh, MESHLINK_WARNING, "Could not resolve node %s to a known address family type.\n", node->name);
280                                         }
281                                 } else {
282                                         logger(mesh, MESHLINK_WARNING, "Node %s is not part of the mesh network.\n", node_name);
283                                 }
284
285                                 pthread_mutex_unlock(&(mesh->mesh_mutex));
286                         }
287                 }
288         }
289         break;
290         }
291
292         catta_s_service_resolver_free(resolver);
293 }
294
295 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) {
296         (void)browser;
297         (void)flags;
298         meshlink_handle_t *mesh = userdata;
299
300         // asserts
301         assert(mesh != NULL);
302         assert(mesh->catta_server != NULL);
303         assert(mesh->catta_poll != NULL);
304
305         /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
306         switch(event) {
307         case CATTA_BROWSER_FAILURE:
308                 catta_simple_poll_quit(mesh->catta_poll);
309                 break;
310
311         case CATTA_BROWSER_NEW:
312                 catta_s_service_resolver_new(mesh->catta_server, interface_, protocol, name, type, domain, CATTA_PROTO_UNSPEC, 0, discovery_resolve_callback, mesh);
313                 break;
314
315         case CATTA_BROWSER_REMOVE:
316         case CATTA_BROWSER_ALL_FOR_NOW:
317         case CATTA_BROWSER_CACHE_EXHAUSTED:
318                 break;
319         }
320 }
321
322 static void *discovery_loop(void *userdata) {
323         meshlink_handle_t *mesh = userdata;
324
325         // asserts
326         assert(mesh != NULL);
327         assert(mesh->catta_poll != NULL);
328
329         catta_simple_poll_loop(mesh->catta_poll);
330
331         return NULL;
332 }
333
334 static void discovery_log_cb(CattaLogLevel level, const char *txt) {
335         meshlink_log_level_t mlevel = MESHLINK_CRITICAL;
336
337         switch(level) {
338         case CATTA_LOG_ERROR:
339                 mlevel = MESHLINK_ERROR;
340                 break;
341
342         case CATTA_LOG_WARN:
343                 mlevel = MESHLINK_WARNING;
344                 break;
345
346         case CATTA_LOG_NOTICE:
347         case CATTA_LOG_INFO:
348                 mlevel = MESHLINK_INFO;
349                 break;
350
351         case CATTA_LOG_DEBUG:
352         default:
353                 mlevel = MESHLINK_DEBUG;
354                 break;
355         }
356
357         logger(NULL, mlevel, "%s\n", txt);
358 }
359
360 bool discovery_start(meshlink_handle_t *mesh) {
361         logger(mesh, MESHLINK_DEBUG, "discovery_start called\n");
362
363         // asserts
364         assert(mesh != NULL);
365         assert(mesh->catta_poll == NULL);
366         assert(mesh->catta_server == NULL);
367         assert(mesh->catta_browser == NULL);
368         assert(mesh->discovery_threadstarted == false);
369         assert(mesh->catta_servicetype == NULL);
370
371         // handle catta logs
372         catta_set_log_function(discovery_log_cb);
373
374         // create service type string
375         size_t servicetype_strlen = sizeof(MESHLINK_MDNS_SERVICE_TYPE) + strlen(mesh->appname) + 1;
376         mesh->catta_servicetype = malloc(servicetype_strlen);
377
378         if(mesh->catta_servicetype == NULL) {
379                 logger(mesh, MESHLINK_ERROR, "Failed to allocate memory for service type string.\n");
380                 goto fail;
381         }
382
383         snprintf(mesh->catta_servicetype, servicetype_strlen, MESHLINK_MDNS_SERVICE_TYPE, mesh->appname);
384
385         // Allocate discovery loop object
386         if(!(mesh->catta_poll = catta_simple_poll_new())) {
387                 logger(mesh, MESHLINK_ERROR, "Failed to create discovery poll object.\n");
388                 goto fail;
389         }
390
391         // generate some unique host name (we actually do not care about it)
392         char hostname[17];
393         generate_rand_string(hostname, sizeof(hostname));
394
395         // Let's set the host name for this server.
396         CattaServerConfig config;
397         catta_server_config_init(&config);
398         config.host_name = catta_strdup(hostname);
399         config.publish_workstation = 0;
400         config.disallow_other_stacks = 0;
401         config.publish_hinfo = 0;
402         config.publish_addresses = 1;
403         config.publish_no_reverse = 1;
404
405         /* Allocate a new server */
406         int error;
407         mesh->catta_server = catta_server_new(catta_simple_poll_get(mesh->catta_poll), &config, discovery_server_callback, mesh, &error);
408
409         /* Free the configuration data */
410         catta_server_config_free(&config);
411
412         /* Check wether creating the server object succeeded */
413         if(!mesh->catta_server) {
414                 logger(mesh, MESHLINK_ERROR, "Failed to create discovery server: %s\n", catta_strerror(error));
415                 goto fail;
416         }
417
418         // Create the service browser
419         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))) {
420                 logger(mesh, MESHLINK_ERROR, "Failed to create discovery service browser: %s\n", catta_strerror(catta_server_errno(mesh->catta_server)));
421                 goto fail;
422         }
423
424         // Start the discovery thread
425         if(pthread_create(&mesh->discovery_thread, NULL, discovery_loop, mesh) != 0) {
426                 logger(mesh, MESHLINK_ERROR, "Could not start discovery thread: %s\n", strerror(errno));
427                 memset(&mesh->discovery_thread, 0, sizeof(mesh)->discovery_thread);
428                 goto fail;
429         }
430
431         mesh->discovery_threadstarted = true;
432
433         return true;
434
435 fail:
436
437         if(mesh->catta_browser != NULL) {
438                 catta_s_service_browser_free(mesh->catta_browser);
439                 mesh->catta_browser = NULL;
440         }
441
442         if(mesh->catta_server != NULL) {
443                 catta_server_free(mesh->catta_server);
444                 mesh->catta_server = NULL;
445         }
446
447         if(mesh->catta_poll != NULL) {
448                 catta_simple_poll_free(mesh->catta_poll);
449                 mesh->catta_poll = NULL;
450         }
451
452         if(mesh->catta_servicetype != NULL) {
453                 free(mesh->catta_servicetype);
454                 mesh->catta_servicetype = NULL;
455         }
456
457         return false;
458 }
459
460 void discovery_stop(meshlink_handle_t *mesh) {
461         logger(mesh, MESHLINK_DEBUG, "discovery_stop called\n");
462
463         // asserts
464         assert(mesh != NULL);
465
466         // Shut down
467         if(mesh->catta_poll) {
468                 catta_simple_poll_quit(mesh->catta_poll);
469         }
470
471         // Wait for the discovery thread to finish
472         if(mesh->discovery_threadstarted == true) {
473                 pthread_join(mesh->discovery_thread, NULL);
474                 mesh->discovery_threadstarted = false;
475         }
476
477         // Clean up resources
478         if(mesh->catta_browser != NULL) {
479                 catta_s_service_browser_free(mesh->catta_browser);
480                 mesh->catta_browser = NULL;
481         }
482
483         if(mesh->catta_group) {
484                 catta_s_entry_group_reset(mesh->catta_group);
485                 catta_s_entry_group_free(mesh->catta_group);
486                 mesh->catta_group = NULL;
487         }
488
489         if(mesh->catta_server != NULL) {
490                 catta_server_free(mesh->catta_server);
491                 mesh->catta_server = NULL;
492         }
493
494         if(mesh->catta_poll != NULL) {
495                 catta_simple_poll_free(mesh->catta_poll);
496                 mesh->catta_poll = NULL;
497         }
498
499         if(mesh->catta_servicetype != NULL) {
500                 free(mesh->catta_servicetype);
501                 mesh->catta_servicetype = NULL;
502         }
503 }