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