]> git.meshlink.io Git - meshlink/blob - src/discovery.c
dclass support within the edge protocol
[meshlink] / src / discovery.c
1
2 #include "meshlink_internal.h"
3 #include "discovery.h"
4 #include "sockaddr.h"
5 #include "logger.h"
6
7 #include <pthread.h>
8
9 #include <avahi-core/core.h>
10 #include <avahi-core/lookup.h>
11 #include <avahi-core/publish.h>
12 #include <avahi-common/simple-watch.h>
13 #include <avahi-common/malloc.h>
14 #include <avahi-common/alternative.h>
15 #include <avahi-common/error.h>
16
17 #include <netinet/in.h>
18
19 #include <uuid/uuid.h>
20
21 #define MESHLINK_MDNS_SERVICE_TYPE "_%s._tcp"
22 #define MESHLINK_MDNS_NAME_KEY "name"
23 #define MESHLINK_MDNS_FINGERPRINT_KEY "fingerprint"
24
25 static void discovery_entry_group_callback(AvahiServer *server, AvahiSEntryGroup *group, AvahiEntryGroupState state, void *userdata)
26 {
27     meshlink_handle_t *mesh = userdata;
28
29     // asserts
30     assert(mesh != NULL);
31     assert(mesh->avahi_server != NULL);
32     assert(mesh->avahi_poll != NULL);
33
34     /* Called whenever the entry group state changes */
35     switch(state)
36     {
37         case AVAHI_ENTRY_GROUP_ESTABLISHED:
38             /* The entry group has been established successfully */
39             logger(mesh, MESHLINK_DEBUG, "Avahi Service successfully established.\n");
40             break;
41
42         case AVAHI_ENTRY_GROUP_COLLISION:
43             logger(mesh, MESHLINK_WARNING, "Avahi Service collision.\n");
44             // @TODO can we just set a new name and retry?
45             break;
46
47         case AVAHI_ENTRY_GROUP_FAILURE :
48             /* Some kind of failure happened while we were registering our services */
49             logger(mesh, MESHLINK_ERROR, "Avahi Entry group failure: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
50             avahi_simple_poll_quit(mesh->avahi_poll);
51             break;
52
53         case AVAHI_ENTRY_GROUP_UNCOMMITED:
54         case AVAHI_ENTRY_GROUP_REGISTERING:
55             ;
56     }
57 }
58
59
60 static void discovery_create_services(meshlink_handle_t *mesh)
61 {
62     char *txt_name = NULL;
63
64     // asserts
65     assert(mesh != NULL);
66     assert(mesh->name != NULL);
67     assert(mesh->myport != NULL);
68     assert(mesh->avahi_server != NULL);
69     assert(mesh->avahi_poll != NULL);
70     assert(mesh->avahi_servicetype != NULL);
71     assert(mesh->self != NULL);
72
73     logger(mesh, MESHLINK_DEBUG, "Adding service\n");
74
75     /* Ifthis is the first time we're called, let's create a new entry group */
76     if(!mesh->avahi_group)
77     {
78         if(!(mesh->avahi_group = avahi_s_entry_group_new(mesh->avahi_server, discovery_entry_group_callback, mesh)))
79         {
80             logger(mesh, MESHLINK_ERROR, "avahi_entry_group_new() failed: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
81             goto fail;
82         }
83     }
84
85     /* Create txt records */
86     size_t txt_name_len = sizeof(MESHLINK_MDNS_NAME_KEY) + 1 + strlen(mesh->name) + 1;
87     txt_name = malloc(txt_name_len);
88
89     if(txt_name == NULL)
90     {
91         logger(mesh, MESHLINK_ERROR, "Could not allocate memory for TXT record\n");
92         goto fail;
93     }
94
95     snprintf(txt_name, txt_name_len, "%s=%s", MESHLINK_MDNS_NAME_KEY, mesh->name);
96
97     char txt_fingerprint[sizeof(MESHLINK_MDNS_FINGERPRINT_KEY) + 1 + MESHLINK_FINGERPRINTLEN + 1];
98     snprintf(txt_fingerprint, sizeof(txt_fingerprint), "%s=%s", MESHLINK_MDNS_FINGERPRINT_KEY, meshlink_get_fingerprint(mesh, (meshlink_node_t *)mesh->self));
99
100     /* Add the service */
101     int ret = 0;
102     if((ret = avahi_server_add_service(mesh->avahi_server, mesh->avahi_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, meshlink_get_fingerprint(mesh, (meshlink_node_t *)mesh->self), mesh->avahi_servicetype, NULL, NULL, atoi(mesh->myport), txt_name, txt_fingerprint, NULL)) < 0)
103     {
104         logger(mesh, MESHLINK_ERROR, "Failed to add service: %s\n", avahi_strerror(ret));
105         goto fail;
106     }
107
108     /* Tell the server to register the service */
109     if((ret = avahi_s_entry_group_commit(mesh->avahi_group)) < 0)
110     {
111         logger(mesh, MESHLINK_ERROR, "Failed to commit entry_group: %s\n", avahi_strerror(ret));
112         goto fail;
113     }
114
115     goto done;
116
117 fail:
118     avahi_simple_poll_quit(mesh->avahi_poll);
119
120 done:
121     if(txt_name)
122         { free(txt_name); }
123 }
124
125 static void discovery_server_callback(AvahiServer *server, AvahiServerState state, void * userdata)
126 {
127         meshlink_handle_t *mesh = userdata;
128
129     // asserts
130     assert(mesh != NULL);
131
132     switch(state)
133     {
134         case AVAHI_SERVER_RUNNING:
135             {
136                 /* The serve has startup successfully and registered its host
137                  * name on the network, so it's time to create our services */
138                 if(!mesh->avahi_group)
139                 {
140                     discovery_create_services(mesh);
141                 }
142             }
143             break;
144
145         case AVAHI_SERVER_COLLISION:
146             {
147                 // asserts
148                 assert(mesh->avahi_server != NULL);
149                 assert(mesh->avahi_poll != NULL);
150
151                 /* A host name collision happened. Let's pick a new name for the server */
152                 uuid_t hostname;
153                 uuid_generate(hostname);
154
155                 char hostnamestr[36+1];
156                 uuid_unparse_lower(hostname, hostnamestr);
157
158                 logger(mesh, MESHLINK_WARNING, "Avahi host name collision, retrying with '%s'\n", hostnamestr);
159                 int result = avahi_server_set_host_name(mesh->avahi_server, hostnamestr);
160
161                 if(result < 0)
162                 {
163                     logger(mesh, MESHLINK_ERROR, "Avahi failed to set new host name: %s\n", avahi_strerror(result));
164                     avahi_simple_poll_quit(mesh->avahi_poll);
165                     return;
166                 }
167             }
168             break;
169
170         case AVAHI_SERVER_REGISTERING:
171             {
172                 /* Let's drop our registered services. When the server is back
173                  * in AVAHI_SERVER_RUNNING state we will register them
174                  * again with the new host name. */
175                 if(mesh->avahi_group)
176                 {
177                     avahi_s_entry_group_reset(mesh->avahi_group);
178                     mesh->avahi_group = NULL;
179                 }
180             }
181             break;
182
183         case AVAHI_SERVER_FAILURE:
184             {
185                 // asserts
186                 assert(mesh->avahi_server != NULL);
187                 assert(mesh->avahi_poll != NULL);
188
189                 /* Terminate on failure */
190                 logger(mesh, MESHLINK_ERROR, "Avahi server failure: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
191                 avahi_simple_poll_quit(mesh->avahi_poll);
192             }
193             break;
194
195         case AVAHI_SERVER_INVALID:
196             break;
197     }
198 }
199
200 static void discovery_resolve_callback(AvahiSServiceResolver *resolver, AvahiIfIndex interface, AvahiProtocol protocol, AvahiResolverEvent event, const char *name, const char *type, const char *domain, const char *host_name, const AvahiAddress *address, uint16_t port, AvahiStringList *txt, AvahiLookupResultFlags flags, void* userdata)
201 {
202     meshlink_handle_t *mesh = userdata;
203
204     // asserts
205     assert(resolver != NULL);
206     assert(mesh != NULL);
207     assert(mesh->avahi_server != NULL);
208
209     /* Called whenever a service has been resolved successfully or timed out */
210     switch(event)
211     {
212         case AVAHI_RESOLVER_FAILURE:
213             {
214                 // asserts
215                 assert(name != NULL);
216                 assert(type != NULL);
217                 assert(domain != NULL);
218
219                 logger(mesh, MESHLINK_WARNING, "(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s\n", name, type, domain, avahi_strerror(avahi_server_errno(mesh->avahi_server)));
220             }
221             break;
222
223         case AVAHI_RESOLVER_FOUND:
224             {
225                 // asserts
226                 assert(name != NULL);
227                 assert(type != NULL);
228                 assert(domain != NULL);
229                 assert(host_name != NULL);
230                 assert(address != NULL);
231                 assert(txt != NULL);
232         
233                 char straddr[AVAHI_ADDRESS_STR_MAX], *strtxt;
234
235                 logger(mesh, MESHLINK_DEBUG, "(Resolver) Service '%s' of type '%s' in domain '%s':\n", name, type, domain);
236
237                 avahi_address_snprint(straddr, sizeof(straddr), address);
238                 strtxt = avahi_string_list_to_string(txt);
239                 logger(mesh, MESHLINK_DEBUG,
240                         "\t%s:%u (%s)\n"
241                         "\tTXT=%s\n"
242                         "\tcookie is %u\n"
243                         "\tis_local: %i\n"
244                         "\twide_area: %i\n"
245                         "\tmulticast: %i\n"
246                         "\tcached: %i\n",
247                         host_name, port, straddr,
248                         strtxt,
249                         avahi_string_list_get_service_cookie(txt),
250                         !!(flags & AVAHI_LOOKUP_RESULT_LOCAL),
251                         !!(flags & AVAHI_LOOKUP_RESULT_WIDE_AREA),
252                         !!(flags & AVAHI_LOOKUP_RESULT_MULTICAST),
253                         !!(flags & AVAHI_LOOKUP_RESULT_CACHED));
254                 avahi_free(strtxt);
255
256                 // retrieve fingerprint
257                 AvahiStringList *node_name_li = avahi_string_list_find(txt, MESHLINK_MDNS_NAME_KEY);
258                 AvahiStringList *node_fp_li = avahi_string_list_find(txt, MESHLINK_MDNS_FINGERPRINT_KEY);
259
260                 if(node_name_li != NULL && node_fp_li != NULL)
261                 {
262                     char *node_name = (char*)avahi_string_list_get_text(node_name_li) + strlen(MESHLINK_MDNS_NAME_KEY);
263                     char *node_fp = (char*)avahi_string_list_get_text(node_fp_li) + strlen(MESHLINK_MDNS_FINGERPRINT_KEY);
264
265                     if(node_name[0] == '=' && node_fp[0] == '=')
266                     {
267                         node_name += 1;
268                         node_fp += 1;
269
270                         meshlink_node_t *node = meshlink_get_node(mesh, node_name);
271
272                         if(node != NULL)
273                         {
274                             logger(mesh, MESHLINK_INFO, "Node %s is part of the mesh network.\n", node->name);
275
276                             sockaddr_t naddress;
277                             memset(&naddress, 0, sizeof(naddress));
278
279                             switch(address->proto)
280                             {
281                                 case AVAHI_PROTO_INET:
282                                     {
283                                         naddress.in.sin_family = AF_INET;
284                                         naddress.in.sin_port = port;
285                                         naddress.in.sin_addr.s_addr = address->data.ipv4.address;
286                                     }
287                                     break;
288
289                                 case AVAHI_PROTO_INET6:
290                                     {
291                                         naddress.in6.sin6_family = AF_INET6;
292                                         naddress.in6.sin6_port = port;
293                                         memcpy(naddress.in6.sin6_addr.s6_addr, address->data.ipv6.address, sizeof(naddress.in6.sin6_addr.s6_addr));
294                                     }
295                                     break;
296
297                                 default:
298                                     naddress.unknown.family = AF_UNKNOWN;
299                                     break;
300                             }
301
302                             if(naddress.unknown.family != AF_UNKNOWN)
303                             {
304                                 meshlink_hint_address(mesh, (meshlink_node_t *)node, (struct sockaddr*)&naddress);
305                             }
306                             else
307                             {
308                                 logger(mesh, MESHLINK_WARNING, "Could not resolve node %s to a known address family type.\n", node->name);
309                             }
310                         }
311                         else
312                         {
313                             logger(mesh, MESHLINK_WARNING, "Node %s is not part of the mesh network.\n", node_name);
314                         }
315                     }
316                     else
317                     {
318                         logger(mesh, MESHLINK_WARNING, "TXT records invalid.\n");
319                     }
320                 }
321                 else
322                 {
323                     logger(mesh, MESHLINK_WARNING, "TXT records missing.\n");
324                 }
325             }
326             break;
327     }
328
329     avahi_s_service_resolver_free(resolver);
330 }
331
332 static void discovery_browse_callback(AvahiSServiceBrowser *browser, AvahiIfIndex interface, AvahiProtocol protocol, AvahiBrowserEvent event, const char *name, const char *type, const char *domain, AvahiLookupResultFlags flags, void* userdata)
333 {
334         meshlink_handle_t *mesh = userdata;
335
336     // asserts
337     assert(mesh != NULL);
338     assert(mesh->avahi_server != NULL);
339     assert(mesh->avahi_poll != NULL);
340
341     /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
342     switch (event)
343     {
344         case AVAHI_BROWSER_FAILURE:
345             {
346                 logger(mesh, MESHLINK_ERROR, "(Browser) %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
347                 avahi_simple_poll_quit(mesh->avahi_poll);
348             }
349             return;
350
351         case AVAHI_BROWSER_NEW:
352             {
353                 // asserts
354                 assert(name != NULL);
355                 assert(type != NULL);
356                 assert(domain != NULL);
357
358                 logger(mesh, MESHLINK_DEBUG, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
359                 /* We ignore the returned resolver object. In the callback
360                    function we free it. Ifthe server is terminated before
361                    the callback function is called the server will free
362                    the resolver for us. */
363                 if(!(avahi_s_service_resolver_new(mesh->avahi_server, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, discovery_resolve_callback, mesh)))
364                 {
365                     logger(mesh, MESHLINK_DEBUG, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_server_errno(mesh->avahi_server)));
366                 }
367             }
368             break;
369
370         case AVAHI_BROWSER_REMOVE:
371             {
372                 // asserts
373                 assert(name != NULL);
374                 assert(type != NULL);
375                 assert(domain != NULL);
376
377                 logger(mesh, MESHLINK_DEBUG, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
378             }
379             break;
380
381         case AVAHI_BROWSER_ALL_FOR_NOW:
382         case AVAHI_BROWSER_CACHE_EXHAUSTED:
383             {
384                 logger(mesh, MESHLINK_DEBUG, "(Browser) %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
385             }
386             break;
387     }
388 }
389
390 static void *discovery_loop(void *userdata)
391 {
392         meshlink_handle_t *mesh = userdata;
393
394     // asserts
395     assert(mesh != NULL);
396     assert(mesh->avahi_poll != NULL);
397
398     avahi_simple_poll_loop(mesh->avahi_poll);
399         
400     return NULL;
401 }
402
403 bool discovery_start(meshlink_handle_t *mesh)
404 {
405     logger(mesh, MESHLINK_DEBUG, "discovery_start called\n");
406
407     // asserts
408     assert(mesh != NULL);
409     assert(mesh->avahi_poll == NULL);
410     assert(mesh->avahi_server == NULL);
411     assert(mesh->avahi_browser == NULL);
412     assert(mesh->discovery_threadstarted == false);
413     assert(mesh->avahi_servicetype == NULL);
414
415     // create service type string
416     size_t servicetype_strlen = sizeof(MESHLINK_MDNS_SERVICE_TYPE) + strlen(mesh->appname) + 1;
417     mesh->avahi_servicetype = malloc(servicetype_strlen);
418
419     if(mesh->avahi_servicetype == NULL)
420     {
421         logger(mesh, MESHLINK_ERROR, "Failed to allocate memory for service type string.\n");
422         goto fail;
423     }
424
425     snprintf(mesh->avahi_servicetype, servicetype_strlen, MESHLINK_MDNS_SERVICE_TYPE, mesh->appname);
426
427     // Allocate discovery loop object
428     if(!(mesh->avahi_poll = avahi_simple_poll_new()))
429     {
430         logger(mesh, MESHLINK_ERROR, "Failed to create discovery poll object.\n");
431                 goto fail;
432     }
433
434     // generate some unique host name (we actually do not care about it)
435     uuid_t hostname;
436     uuid_generate(hostname);
437
438     char hostnamestr[36+1];
439     uuid_unparse_lower(hostname, hostnamestr);
440
441     // Let's set the host name for this server.
442     AvahiServerConfig config;
443     avahi_server_config_init(&config);
444     config.host_name = avahi_strdup(hostnamestr);
445     config.publish_workstation = 0;
446     config.disallow_other_stacks = 0;
447     config.publish_hinfo = 0;
448     config.publish_addresses = 1;
449     config.publish_no_reverse = 1;
450
451     /* Allocate a new server */
452     int error;
453     mesh->avahi_server = avahi_server_new(avahi_simple_poll_get(mesh->avahi_poll), &config, discovery_server_callback, mesh, &error);
454
455     /* Free the configuration data */
456     avahi_server_config_free(&config);
457
458     /* Check wether creating the server object succeeded */
459     if(!mesh->avahi_server)
460     {
461         logger(mesh, MESHLINK_ERROR, "Failed to create discovery server: %s\n", avahi_strerror(error));
462         goto fail;
463     }
464
465     // Create the service browser
466     if(!(mesh->avahi_browser = avahi_s_service_browser_new(mesh->avahi_server, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, mesh->avahi_servicetype, NULL, 0, discovery_browse_callback, mesh)))
467     {
468         logger(mesh, MESHLINK_ERROR, "Failed to create discovery service browser: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
469         goto fail;
470     }
471
472         // Start the discovery thread
473         if(pthread_create(&mesh->discovery_thread, NULL, discovery_loop, mesh) != 0)
474     {
475                 logger(mesh, MESHLINK_ERROR, "Could not start discovery thread: %s\n", strerror(errno));
476                 memset(&mesh->discovery_thread, 0, sizeof mesh->discovery_thread);
477                 goto fail;
478         }
479
480         mesh->discovery_threadstarted = true;
481
482         return true;
483
484 fail:
485     if(mesh->avahi_browser != NULL)
486     {
487         avahi_s_service_browser_free(mesh->avahi_browser);
488         mesh->avahi_browser = NULL;
489     }
490
491     if(mesh->avahi_server != NULL)
492     {
493         avahi_server_free(mesh->avahi_server);
494         mesh->avahi_server = NULL;
495     }
496
497     if(mesh->avahi_poll != NULL)
498     {
499         avahi_simple_poll_free(mesh->avahi_poll);
500         mesh->avahi_poll = NULL;
501     }
502
503     if(mesh->avahi_servicetype != NULL)
504     {
505         free(mesh->avahi_servicetype);
506         mesh->avahi_servicetype = NULL;
507     }
508
509     return false;
510 }
511
512 void discovery_stop(meshlink_handle_t *mesh)
513 {
514     logger(mesh, MESHLINK_DEBUG, "discovery_stop called\n");
515
516     // asserts
517     assert(mesh != NULL);
518
519         // Shut down
520     if(mesh->avahi_poll)
521     {
522         avahi_simple_poll_quit(mesh->avahi_poll);
523     }
524
525         // Wait for the discovery thread to finish
526     if(mesh->discovery_threadstarted == true)
527     {
528         pthread_join(mesh->discovery_thread, NULL);
529         mesh->discovery_threadstarted = false;
530     }
531
532         // Clean up resources
533     if(mesh->avahi_browser != NULL)
534     {
535         avahi_s_service_browser_free(mesh->avahi_browser);
536         mesh->avahi_browser = NULL;
537     }
538
539     if(mesh->avahi_server != NULL)
540     {
541         avahi_server_free(mesh->avahi_server);
542         mesh->avahi_server = NULL;
543     }
544
545     if(mesh->avahi_poll != NULL)
546     {
547         avahi_simple_poll_free(mesh->avahi_poll);
548         mesh->avahi_poll = NULL;
549     }
550
551     if(mesh->avahi_servicetype != NULL)
552     {
553         free(mesh->avahi_servicetype);
554         mesh->avahi_servicetype = NULL;
555     }
556 }