]> git.meshlink.io Git - meshlink/blob - src/discovery.c
windows compatibility
[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 {
25     for(size_t i = 0; i < (size - 1); ++i)
26     {
27         buffer[i] = 'a' + (rand() % ('z' - 'a' + 1));
28     }
29
30     buffer[size-1] = '\0';
31 }
32
33 static void discovery_entry_group_callback(CattaServer *server, CattaSEntryGroup *group, CattaEntryGroupState state, void *userdata)
34 {
35     meshlink_handle_t *mesh = userdata;
36
37     // asserts
38     assert(mesh != NULL);
39     assert(mesh->catta_server != NULL);
40     assert(mesh->catta_poll != NULL);
41
42     pthread_mutex_lock(&(mesh->mesh_mutex));
43
44     /* Called whenever the entry group state changes */
45     switch(state)
46     {
47         case CATTA_ENTRY_GROUP_ESTABLISHED:
48             /* The entry group has been established successfully */
49             logger(mesh, MESHLINK_DEBUG, "Catta Service successfully established.\n");
50             break;
51
52         case CATTA_ENTRY_GROUP_COLLISION:
53             logger(mesh, MESHLINK_WARNING, "Catta Service collision.\n");
54             // @TODO can we just set a new name and retry?
55             break;
56
57         case CATTA_ENTRY_GROUP_FAILURE :
58             /* Some kind of failure happened while we were registering our services */
59             logger(mesh, MESHLINK_ERROR, "Catta Entry group failure: %s\n", catta_strerror(catta_server_errno(mesh->catta_server)));
60             catta_simple_poll_quit(mesh->catta_poll);
61             break;
62
63         case CATTA_ENTRY_GROUP_UNCOMMITED:
64         case CATTA_ENTRY_GROUP_REGISTERING:
65             ;
66     }
67
68     pthread_mutex_unlock(&(mesh->mesh_mutex));
69 }
70
71
72 static void discovery_create_services(meshlink_handle_t *mesh)
73 {
74     char *txt_name = NULL;
75
76     // asserts
77     assert(mesh != NULL);
78     assert(mesh->name != NULL);
79     assert(mesh->myport != NULL);
80     assert(mesh->catta_server != NULL);
81     assert(mesh->catta_poll != NULL);
82     assert(mesh->catta_servicetype != NULL);
83     assert(mesh->self != NULL);
84
85     pthread_mutex_lock(&(mesh->mesh_mutex));
86
87     logger(mesh, MESHLINK_DEBUG, "Adding service\n");
88
89     /* Ifthis is the first time we're called, let's create a new entry group */
90     if(!mesh->catta_group)
91     {
92         if(!(mesh->catta_group = catta_s_entry_group_new(mesh->catta_server, discovery_entry_group_callback, mesh)))
93         {
94             logger(mesh, MESHLINK_ERROR, "catta_entry_group_new() failed: %s\n", catta_strerror(catta_server_errno(mesh->catta_server)));
95             goto fail;
96         }
97     }
98
99     /* Create txt records */
100     size_t txt_name_len = sizeof(MESHLINK_MDNS_NAME_KEY) + 1 + strlen(mesh->name) + 1;
101     txt_name = malloc(txt_name_len);
102
103     if(txt_name == NULL)
104     {
105         logger(mesh, MESHLINK_ERROR, "Could not allocate memory for TXT record\n");
106         goto fail;
107     }
108
109     snprintf(txt_name, txt_name_len, "%s=%s", MESHLINK_MDNS_NAME_KEY, mesh->name);
110
111     char txt_fingerprint[sizeof(MESHLINK_MDNS_FINGERPRINT_KEY) + 1 + MESHLINK_FINGERPRINTLEN + 1];
112     snprintf(txt_fingerprint, sizeof(txt_fingerprint), "%s=%s", MESHLINK_MDNS_FINGERPRINT_KEY, meshlink_get_fingerprint(mesh, (meshlink_node_t *)mesh->self));
113
114     /* Add the service */
115     int ret = 0;
116     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)
117     {
118         logger(mesh, MESHLINK_ERROR, "Failed to add service: %s\n", catta_strerror(ret));
119         goto fail;
120     }
121
122     /* Tell the server to register the service */
123     if((ret = catta_s_entry_group_commit(mesh->catta_group)) < 0)
124     {
125         logger(mesh, MESHLINK_ERROR, "Failed to commit entry_group: %s\n", catta_strerror(ret));
126         goto fail;
127     }
128
129     goto done;
130
131 fail:
132     catta_simple_poll_quit(mesh->catta_poll);
133
134 done:
135     if(txt_name)
136         { free(txt_name); }
137
138     pthread_mutex_unlock(&(mesh->mesh_mutex));
139 }
140
141 static void discovery_server_callback(CattaServer *server, CattaServerState state, void * userdata)
142 {
143         meshlink_handle_t *mesh = userdata;
144
145     // asserts
146     assert(mesh != NULL);
147     
148     pthread_mutex_lock(&(mesh->mesh_mutex));
149
150     switch(state)
151     {
152         case CATTA_SERVER_RUNNING:
153             {
154                 /* The serve has startup successfully and registered its host
155                  * name on the network, so it's time to create our services */
156                 if(!mesh->catta_group)
157                 {
158                     discovery_create_services(mesh);
159                 }
160             }
161             break;
162
163         case CATTA_SERVER_COLLISION:
164             {
165                 // asserts
166                 assert(mesh->catta_server != NULL);
167                 assert(mesh->catta_poll != NULL);
168
169                 /* A host name collision happened. Let's pick a new name for the server */
170                 char hostname[17];
171                 generate_rand_string(hostname, sizeof(hostname));
172
173                 logger(mesh, MESHLINK_WARNING, "Catta host name collision, retrying with '%s'\n", hostname);
174                 int result = catta_server_set_host_name(mesh->catta_server, hostname);
175
176                 if(result < 0)
177                 {
178                     logger(mesh, MESHLINK_ERROR, "Catta failed to set new host name: %s\n", catta_strerror(result));
179                     catta_simple_poll_quit(mesh->catta_poll);
180                 }
181             }
182             break;
183
184         case CATTA_SERVER_REGISTERING:
185             {
186                 /* Let's drop our registered services. When the server is back
187                  * in CATTA_SERVER_RUNNING state we will register them
188                  * again with the new host name. */
189                 if(mesh->catta_group)
190                 {
191                     catta_s_entry_group_reset(mesh->catta_group);
192                     mesh->catta_group = NULL;
193                 }
194             }
195             break;
196
197         case CATTA_SERVER_FAILURE:
198             {
199                 // asserts
200                 assert(mesh->catta_server != NULL);
201                 assert(mesh->catta_poll != NULL);
202
203                 /* Terminate on failure */
204                 logger(mesh, MESHLINK_ERROR, "Catta server failure: %s\n", catta_strerror(catta_server_errno(mesh->catta_server)));
205                 catta_simple_poll_quit(mesh->catta_poll);
206             }
207             break;
208
209         case CATTA_SERVER_INVALID:
210             break;
211     }
212
213     pthread_mutex_unlock(&(mesh->mesh_mutex));
214 }
215
216 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)
217 {
218     meshlink_handle_t *mesh = userdata;
219
220     // asserts
221     assert(resolver != NULL);
222     assert(mesh != NULL);
223     assert(mesh->catta_server != NULL);
224
225     pthread_mutex_lock(&(mesh->mesh_mutex));
226
227     /* Called whenever a service has been resolved successfully or timed out */
228     switch(event)
229     {
230         case CATTA_RESOLVER_FAILURE:
231             {
232                 // asserts
233                 assert(name != NULL);
234                 assert(type != NULL);
235                 assert(domain != NULL);
236
237                 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)));
238             }
239             break;
240
241         case CATTA_RESOLVER_FOUND:
242             {
243                 // asserts
244                 assert(name != NULL);
245                 assert(type != NULL);
246                 assert(domain != NULL);
247                 assert(host_name != NULL);
248                 assert(address != NULL);
249                 assert(txt != NULL);
250         
251                 char straddr[CATTA_ADDRESS_STR_MAX], *strtxt;
252
253                 logger(mesh, MESHLINK_DEBUG, "(Resolver) Service '%s' of type '%s' in domain '%s':\n", name, type, domain);
254
255                 catta_address_snprint(straddr, sizeof(straddr), address);
256                 strtxt = catta_string_list_to_string(txt);
257                 logger(mesh, MESHLINK_DEBUG,
258                         "\t%s:%u (%s)\n"
259                         "\tTXT=%s\n"
260                         "\tcookie is %u\n"
261                         "\tis_local: %i\n"
262                         "\twide_area: %i\n"
263                         "\tmulticast: %i\n"
264                         "\tcached: %i\n",
265                         host_name, port, straddr,
266                         strtxt,
267                         catta_string_list_get_service_cookie(txt),
268                         !!(flags & CATTA_LOOKUP_RESULT_LOCAL),
269                         !!(flags & CATTA_LOOKUP_RESULT_WIDE_AREA),
270                         !!(flags & CATTA_LOOKUP_RESULT_MULTICAST),
271                         !!(flags & CATTA_LOOKUP_RESULT_CACHED));
272                 catta_free(strtxt);
273
274                 // retrieve fingerprint
275                 CattaStringList *node_name_li = catta_string_list_find(txt, MESHLINK_MDNS_NAME_KEY);
276                 CattaStringList *node_fp_li = catta_string_list_find(txt, MESHLINK_MDNS_FINGERPRINT_KEY);
277
278                 if(node_name_li != NULL && node_fp_li != NULL)
279                 {
280                     char *node_name = (char*)catta_string_list_get_text(node_name_li) + strlen(MESHLINK_MDNS_NAME_KEY);
281                     char *node_fp = (char*)catta_string_list_get_text(node_fp_li) + strlen(MESHLINK_MDNS_FINGERPRINT_KEY);
282
283                     if(node_name[0] == '=' && node_fp[0] == '=')
284                     {
285                         node_name += 1;
286                         node_fp += 1;
287
288                         meshlink_node_t *node = meshlink_get_node(mesh, node_name);
289
290                         if(node != NULL)
291                         {
292                             logger(mesh, MESHLINK_INFO, "Node %s is part of the mesh network.\n", node->name);
293
294                             sockaddr_t naddress;
295                             memset(&naddress, 0, sizeof(naddress));
296
297                             switch(address->proto)
298                             {
299                                 case CATTA_PROTO_INET:
300                                     {
301                                         naddress.in.sin_family = AF_INET;
302                                         naddress.in.sin_port = htons(port);
303                                         naddress.in.sin_addr.s_addr = address->data.ipv4.address;
304                                     }
305                                     break;
306
307                                 case CATTA_PROTO_INET6:
308                                     {
309                                         naddress.in6.sin6_family = AF_INET6;
310                                         naddress.in6.sin6_port = htons(port);
311                                         memcpy(naddress.in6.sin6_addr.s6_addr, address->data.ipv6.address, sizeof(naddress.in6.sin6_addr.s6_addr));
312                                     }
313                                     break;
314
315                                 default:
316                                     naddress.unknown.family = AF_UNKNOWN;
317                                     break;
318                             }
319
320                             if(naddress.unknown.family != AF_UNKNOWN)
321                             {
322                                 meshlink_hint_address(mesh, (meshlink_node_t *)node, (struct sockaddr*)&naddress);
323                             }
324                             else
325                             {
326                                 logger(mesh, MESHLINK_WARNING, "Could not resolve node %s to a known address family type.\n", node->name);
327                             }
328                         }
329                         else
330                         {
331                             logger(mesh, MESHLINK_WARNING, "Node %s is not part of the mesh network.\n", node_name);
332                         }
333                     }
334                     else
335                     {
336                         logger(mesh, MESHLINK_WARNING, "TXT records invalid.\n");
337                     }
338                 }
339                 else
340                 {
341                     logger(mesh, MESHLINK_WARNING, "TXT records missing.\n");
342                 }
343             }
344             break;
345     }
346
347     catta_s_service_resolver_free(resolver);
348
349     pthread_mutex_unlock(&(mesh->mesh_mutex));
350 }
351
352 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)
353 {
354         meshlink_handle_t *mesh = userdata;
355
356     // asserts
357     assert(mesh != NULL);
358     assert(mesh->catta_server != NULL);
359     assert(mesh->catta_poll != NULL);
360
361     pthread_mutex_lock(&(mesh->mesh_mutex));
362
363     /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
364     switch (event)
365     {
366         case CATTA_BROWSER_FAILURE:
367             {
368                 logger(mesh, MESHLINK_ERROR, "(Browser) %s\n", catta_strerror(catta_server_errno(mesh->catta_server)));
369                 catta_simple_poll_quit(mesh->catta_poll);
370             }
371             break;
372
373         case CATTA_BROWSER_NEW:
374             {
375                 // asserts
376                 assert(name != NULL);
377                 assert(type != NULL);
378                 assert(domain != NULL);
379
380                 logger(mesh, MESHLINK_DEBUG, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
381                 /* We ignore the returned resolver object. In the callback
382                    function we free it. Ifthe server is terminated before
383                    the callback function is called the server will free
384                    the resolver for us. */
385                 if(!(catta_s_service_resolver_new(mesh->catta_server, interface_, protocol, name, type, domain, CATTA_PROTO_UNSPEC, 0, discovery_resolve_callback, mesh)))
386                 {
387                     logger(mesh, MESHLINK_DEBUG, "Failed to resolve service '%s': %s\n", name, catta_strerror(catta_server_errno(mesh->catta_server)));
388                 }
389             }
390             break;
391
392         case CATTA_BROWSER_REMOVE:
393             {
394                 // asserts
395                 assert(name != NULL);
396                 assert(type != NULL);
397                 assert(domain != NULL);
398
399                 logger(mesh, MESHLINK_DEBUG, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
400             }
401             break;
402
403         case CATTA_BROWSER_ALL_FOR_NOW:
404         case CATTA_BROWSER_CACHE_EXHAUSTED:
405             {
406                 logger(mesh, MESHLINK_DEBUG, "(Browser) %s\n", event == CATTA_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
407             }
408             break;
409     }
410     
411     pthread_mutex_unlock(&(mesh->mesh_mutex));
412 }
413
414 static void *discovery_loop(void *userdata)
415 {
416         meshlink_handle_t *mesh = userdata;
417
418     // asserts
419     assert(mesh != NULL);
420     assert(mesh->catta_poll != NULL);
421
422     catta_simple_poll_loop(mesh->catta_poll);
423         
424     return NULL;
425 }
426
427 static void discovery_log_cb(CattaLogLevel level, const char *txt)
428 {
429     meshlink_log_level_t mlevel = MESHLINK_CRITICAL;
430
431     switch(level)
432     {
433     case CATTA_LOG_ERROR:
434         mlevel = MESHLINK_ERROR;
435         break;
436
437     case CATTA_LOG_WARN:
438         mlevel = MESHLINK_WARNING;
439         break;
440
441     case CATTA_LOG_NOTICE:
442     case CATTA_LOG_INFO:
443         mlevel = MESHLINK_INFO;
444         break;
445
446     case CATTA_LOG_DEBUG:
447         mlevel = MESHLINK_DEBUG;
448         break;
449     }
450
451     logger(NULL, mlevel, "%s\n", txt);
452 }
453
454 bool discovery_start(meshlink_handle_t *mesh)
455 {
456     logger(mesh, MESHLINK_DEBUG, "discovery_start called\n");
457
458     // asserts
459     assert(mesh != NULL);
460     assert(mesh->catta_poll == NULL);
461     assert(mesh->catta_server == NULL);
462     assert(mesh->catta_browser == NULL);
463     assert(mesh->discovery_threadstarted == false);
464     assert(mesh->catta_servicetype == NULL);
465
466     // handle catta logs
467     catta_set_log_function(discovery_log_cb);
468
469     // create service type string
470     size_t servicetype_strlen = sizeof(MESHLINK_MDNS_SERVICE_TYPE) + strlen(mesh->appname) + 1;
471     mesh->catta_servicetype = malloc(servicetype_strlen);
472
473     if(mesh->catta_servicetype == NULL)
474     {
475         logger(mesh, MESHLINK_ERROR, "Failed to allocate memory for service type string.\n");
476         goto fail;
477     }
478
479     snprintf(mesh->catta_servicetype, servicetype_strlen, MESHLINK_MDNS_SERVICE_TYPE, mesh->appname);
480
481     // Allocate discovery loop object
482     if(!(mesh->catta_poll = catta_simple_poll_new()))
483     {
484         logger(mesh, MESHLINK_ERROR, "Failed to create discovery poll object.\n");
485                 goto fail;
486     }
487
488     // generate some unique host name (we actually do not care about it)
489     char hostname[17];
490     generate_rand_string(hostname, sizeof(hostname));
491
492     // Let's set the host name for this server.
493     CattaServerConfig config;
494     catta_server_config_init(&config);
495     config.host_name = catta_strdup(hostname);
496     config.publish_workstation = 0;
497     config.disallow_other_stacks = 0;
498     config.publish_hinfo = 0;
499     config.publish_addresses = 1;
500     config.publish_no_reverse = 1;
501
502     /* Allocate a new server */
503     int error;
504     mesh->catta_server = catta_server_new(catta_simple_poll_get(mesh->catta_poll), &config, discovery_server_callback, mesh, &error);
505
506     /* Free the configuration data */
507     catta_server_config_free(&config);
508
509     /* Check wether creating the server object succeeded */
510     if(!mesh->catta_server)
511     {
512         logger(mesh, MESHLINK_ERROR, "Failed to create discovery server: %s\n", catta_strerror(error));
513         goto fail;
514     }
515
516     // Create the service browser
517     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)))
518     {
519         logger(mesh, MESHLINK_ERROR, "Failed to create discovery service browser: %s\n", catta_strerror(catta_server_errno(mesh->catta_server)));
520         goto fail;
521     }
522
523         // Start the discovery thread
524         if(pthread_create(&mesh->discovery_thread, NULL, discovery_loop, mesh) != 0)
525     {
526                 logger(mesh, MESHLINK_ERROR, "Could not start discovery thread: %s\n", strerror(errno));
527                 memset(&mesh->discovery_thread, 0, sizeof mesh->discovery_thread);
528                 goto fail;
529         }
530
531         mesh->discovery_threadstarted = true;
532
533         return true;
534
535 fail:
536     if(mesh->catta_browser != NULL)
537     {
538         catta_s_service_browser_free(mesh->catta_browser);
539         mesh->catta_browser = NULL;
540     }
541
542     if(mesh->catta_server != NULL)
543     {
544         catta_server_free(mesh->catta_server);
545         mesh->catta_server = NULL;
546     }
547
548     if(mesh->catta_poll != NULL)
549     {
550         catta_simple_poll_free(mesh->catta_poll);
551         mesh->catta_poll = NULL;
552     }
553
554     if(mesh->catta_servicetype != NULL)
555     {
556         free(mesh->catta_servicetype);
557         mesh->catta_servicetype = NULL;
558     }
559
560     return false;
561 }
562
563 void discovery_stop(meshlink_handle_t *mesh)
564 {
565     logger(mesh, MESHLINK_DEBUG, "discovery_stop called\n");
566
567     // asserts
568     assert(mesh != NULL);
569
570         // Shut down
571     if(mesh->catta_poll)
572     {
573         catta_simple_poll_quit(mesh->catta_poll);
574     }
575
576         // Wait for the discovery thread to finish
577     if(mesh->discovery_threadstarted == true)
578     {
579         pthread_join(mesh->discovery_thread, NULL);
580         mesh->discovery_threadstarted = false;
581     }
582
583         // Clean up resources
584     if(mesh->catta_browser != NULL)
585     {
586         catta_s_service_browser_free(mesh->catta_browser);
587         mesh->catta_browser = NULL;
588     }
589
590     if(mesh->catta_group)
591     {
592         catta_s_entry_group_reset(mesh->catta_group);
593         catta_s_entry_group_free(mesh->catta_group);
594         mesh->catta_group = NULL;
595     }
596
597     if(mesh->catta_server != NULL)
598     {
599         catta_server_free(mesh->catta_server);
600         mesh->catta_server = NULL;
601     }
602
603     if(mesh->catta_poll != NULL)
604     {
605         catta_simple_poll_free(mesh->catta_poll);
606         mesh->catta_poll = NULL;
607     }
608
609     if(mesh->catta_servicetype != NULL)
610     {
611         free(mesh->catta_servicetype);
612         mesh->catta_servicetype = NULL;
613     }
614 }