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