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