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