]> git.meshlink.io Git - meshlink/blob - src/discovery.c
changed name of SRV record to fingerprint
[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             fprintf(stderr, "Service successfully established.\n");
39             break;
40
41         case AVAHI_ENTRY_GROUP_COLLISION:
42             fprintf(stderr, "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             fprintf(stderr, "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     fprintf(stderr, "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             fprintf(stderr, "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         fprintf(stderr, "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         fprintf(stderr, "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         fprintf(stderr, "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                 fprintf(stderr, "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                     fprintf(stderr, "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                 fprintf(stderr, "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                 fprintf(stderr, "(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                 fprintf(stderr, "(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                 fprintf(stderr,
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 = avahi_string_list_get_text(node_name_li) + strlen(MESHLINK_MDNS_NAME_KEY) + 1;
262                     char *node_fp = avahi_string_list_get_text(node_fp_li) + strlen(MESHLINK_MDNS_FINGERPRINT_KEY) + 1;
263
264                     meshlink_node_t *node = meshlink_get_node(mesh, node_name);
265
266                     if(node != NULL)
267                     {
268                         fprintf(stderr, "Node %s is part of the mesh network.\n", node->name);
269
270                         sockaddr_t naddress;
271                         memset(&naddress, 0, sizeof(naddress));
272
273                         switch(address->proto)
274                         {
275                             case AVAHI_PROTO_INET:
276                                 {
277                                     naddress.in.sin_family = AF_INET;
278                                     naddress.in.sin_port = port;
279                                     naddress.in.sin_addr.s_addr = address->data.ipv4.address;
280                                 }
281                                 break;
282
283                             case AVAHI_PROTO_INET6:
284                                 {
285                                     naddress.in6.sin6_family = AF_INET6;
286                                     naddress.in6.sin6_port = port;
287                                     memcpy(naddress.in6.sin6_addr.s6_addr, address->data.ipv6.address, sizeof(naddress.in6.sin6_addr.s6_addr));
288                                 }
289                                 break;
290
291                             default:
292                                 naddress.unknown.family = AF_UNKNOWN;
293                                 break;
294                         }
295
296                         if(naddress.unknown.family != AF_UNKNOWN)
297                         {
298                             meshlink_hint_address(mesh, (meshlink_node_t *)node, (struct sockaddr*)&naddress);
299                         }
300                         else
301                         {
302                             fprintf(stderr, "Could not resolve node %s to a known address family type.\n", node->name);
303                         }
304                     }
305                     else
306                     {
307                         fprintf(stderr, "Node %s is not part of the mesh network.\n", node_name);
308                     }
309                 }
310                 else
311                 {
312                     fprintf(stderr, "TXT records missing.\n");
313                 }
314             }
315             break;
316     }
317
318     avahi_s_service_resolver_free(resolver);
319 }
320
321 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)
322 {
323         meshlink_handle_t *mesh = userdata;
324
325     // asserts
326     assert(mesh != NULL);
327     assert(mesh->avahi_server != NULL);
328     assert(mesh->avahi_poll != NULL);
329
330     /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
331     switch (event)
332     {
333         case AVAHI_BROWSER_FAILURE:
334             {
335                 fprintf(stderr, "(Browser) %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
336                 avahi_simple_poll_quit(mesh->avahi_poll);
337             }
338             return;
339
340         case AVAHI_BROWSER_NEW:
341             {
342                 // asserts
343                 assert(name != NULL);
344                 assert(type != NULL);
345                 assert(domain != NULL);
346
347                 fprintf(stderr, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
348                 /* We ignore the returned resolver object. In the callback
349                    function we free it. Ifthe server is terminated before
350                    the callback function is called the server will free
351                    the resolver for us. */
352                 if(!(avahi_s_service_resolver_new(mesh->avahi_server, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, discovery_resolve_callback, mesh)))
353                 {
354                     fprintf(stderr, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_server_errno(mesh->avahi_server)));
355                 }
356             }
357             break;
358
359         case AVAHI_BROWSER_REMOVE:
360             {
361                 // asserts
362                 assert(name != NULL);
363                 assert(type != NULL);
364                 assert(domain != NULL);
365
366                 fprintf(stderr, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
367             }
368             break;
369
370         case AVAHI_BROWSER_ALL_FOR_NOW:
371         case AVAHI_BROWSER_CACHE_EXHAUSTED:
372             {
373                 fprintf(stderr, "(Browser) %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
374             }
375             break;
376     }
377 }
378
379 static void *discovery_loop(void *userdata)
380 {
381         meshlink_handle_t *mesh = userdata;
382
383     // asserts
384     assert(mesh != NULL);
385     assert(mesh->avahi_poll != NULL);
386
387     avahi_simple_poll_loop(mesh->avahi_poll);
388         
389     return NULL;
390 }
391
392 bool discovery_start(meshlink_handle_t *mesh)
393 {
394     // asserts
395     assert(mesh != NULL);
396     assert(mesh->avahi_poll == NULL);
397     assert(mesh->avahi_server == NULL);
398     assert(mesh->avahi_browser == NULL);
399     assert(mesh->discovery_threadstarted == false);
400     assert(mesh->avahi_servicetype == NULL);
401
402     // create service type string
403     size_t servicetype_strlen = sizeof(MESHLINK_MDNS_SERVICE_TYPE) + strlen(mesh->appname) + 1;
404     mesh->avahi_servicetype = malloc(servicetype_strlen);
405
406     if(mesh->avahi_servicetype == NULL)
407     {
408         fprintf(stderr, "Failed to allocate memory for service type string.\n");
409         goto fail;
410     }
411
412     snprintf(mesh->avahi_servicetype, servicetype_strlen, MESHLINK_MDNS_SERVICE_TYPE, mesh->appname);
413
414     // Allocate discovery loop object
415     if(!(mesh->avahi_poll = avahi_simple_poll_new()))
416     {
417         fprintf(stderr, "Failed to create discovery poll object.\n");
418                 goto fail;
419     }
420
421     // generate some unique host name (we actually do not care about it)
422     uuid_t hostname;
423     uuid_generate(hostname);
424
425     char hostnamestr[36+1];
426     uuid_unparse_lower(hostname, hostnamestr);
427
428     // Let's set the host name for this server.
429     AvahiServerConfig config;
430     avahi_server_config_init(&config);
431     config.host_name = avahi_strdup(hostnamestr);
432     config.publish_workstation = 0;
433     config.disallow_other_stacks = 0;
434     config.publish_hinfo = 0;
435     config.publish_addresses = 1;
436     config.publish_no_reverse = 1;
437
438     /* Allocate a new server */
439     int error;
440     mesh->avahi_server = avahi_server_new(avahi_simple_poll_get(mesh->avahi_poll), &config, discovery_server_callback, mesh, &error);
441
442     /* Free the configuration data */
443     avahi_server_config_free(&config);
444
445     /* Check wether creating the server object succeeded */
446     if(!mesh->avahi_server)
447     {
448         fprintf(stderr, "Failed to create discovery server: %s\n", avahi_strerror(error));
449         goto fail;
450     }
451
452     // Create the service browser
453     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)))
454     {
455         fprintf(stderr, "Failed to create discovery service browser: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
456         goto fail;
457     }
458
459         // Start the discovery thread
460         if(pthread_create(&mesh->discovery_thread, NULL, discovery_loop, mesh) != 0)
461     {
462                 fprintf(stderr, "Could not start discovery thread: %s\n", strerror(errno));
463                 memset(&mesh->discovery_thread, 0, sizeof mesh->discovery_thread);
464                 goto fail;
465         }
466
467         mesh->discovery_threadstarted = true;
468
469         return true;
470
471 fail:
472     if(mesh->avahi_browser != NULL)
473     {
474         avahi_s_service_browser_free(mesh->avahi_browser);
475         mesh->avahi_browser = NULL;
476     }
477
478     if(mesh->avahi_server != NULL)
479     {
480         avahi_server_free(mesh->avahi_server);
481         mesh->avahi_server = NULL;
482     }
483
484     if(mesh->avahi_poll != NULL)
485     {
486         avahi_simple_poll_free(mesh->avahi_poll);
487         mesh->avahi_poll = NULL;
488     }
489
490     if(mesh->avahi_servicetype != NULL)
491     {
492         free(mesh->avahi_servicetype);
493         mesh->avahi_servicetype = NULL;
494     }
495
496     return false;
497 }
498
499 void discovery_stop(meshlink_handle_t *mesh)
500 {
501     // asserts
502     assert(mesh != NULL);
503     assert(mesh->avahi_poll != NULL);
504     assert(mesh->avahi_server != NULL);
505     assert(mesh->avahi_browser != NULL);
506     assert(mesh->discovery_threadstarted == true);
507     assert(mesh->avahi_servicetype != NULL);
508
509         // Shut down 
510         avahi_simple_poll_quit(mesh->avahi_poll);
511
512         // Wait for the discovery thread to finish
513         pthread_join(mesh->discovery_thread, NULL);
514
515         // Clean up resources
516     avahi_s_service_browser_free(mesh->avahi_browser);
517     mesh->avahi_browser = NULL;
518
519     avahi_server_free(mesh->avahi_server);
520     mesh->avahi_server = NULL;
521
522     avahi_simple_poll_free(mesh->avahi_poll);
523     mesh->avahi_poll = NULL;
524
525     free(mesh->avahi_servicetype);
526     mesh->avahi_servicetype = NULL;
527 }