]> git.meshlink.io Git - meshlink/blob - src/discovery.c
ignore unknown address types (just in case)
[meshlink] / src / discovery.c
1
2 #include "meshlink_internal.h"
3 #include "discovery.h"
4
5 #include <pthread.h>
6
7 #include <avahi-core/core.h>
8 #include <avahi-core/lookup.h>
9 #include <avahi-core/publish.h>
10 #include <avahi-common/simple-watch.h>
11 #include <avahi-common/malloc.h>
12 #include <avahi-common/alternative.h>
13 #include <avahi-common/error.h>
14
15 #include <netinet/in.h>
16
17 #define MESHLINK_MDNS_SERVICE_TYPE "_meshlink._tcp"
18 //#define MESHLINK_MDNS_SERVICE_NAME "Meshlink"
19 #define MESHLINK_MDNS_FINGERPRINT_KEY "fingerprint"
20
21 static void discovery_resolve_callback(
22     AvahiSServiceResolver *resolver,
23     AVAHI_GCC_UNUSED AvahiIfIndex interface,
24     AVAHI_GCC_UNUSED AvahiProtocol protocol,
25     AvahiResolverEvent event,
26     const char *name,
27     const char *type,
28     const char *domain,
29     const char *host_name,
30     const AvahiAddress *address,
31     uint16_t port,
32     AvahiStringList *txt,
33     AvahiLookupResultFlags flags,
34     AVAHI_GCC_UNUSED void* userdata)
35 {
36
37     meshlink_handle_t *mesh = userdata;
38
39     /* Called whenever a service has been resolved successfully or timed out */
40
41     switch (event)
42     {
43         case AVAHI_RESOLVER_FAILURE:
44             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)));
45             break;
46
47         case AVAHI_RESOLVER_FOUND:
48         {
49             char straddr[AVAHI_ADDRESS_STR_MAX], *strtxt;
50
51             fprintf(stderr, "(Resolver) Service '%s' of type '%s' in domain '%s':\n", name, type, domain);
52
53             avahi_address_snprint(straddr, sizeof(straddr), address);
54             strtxt = avahi_string_list_to_string(txt);
55             fprintf(stderr,
56                     "\t%s:%u (%s)\n"
57                     "\tTXT=%s\n"
58                     "\tcookie is %u\n"
59                     "\tis_local: %i\n"
60                     "\twide_area: %i\n"
61                     "\tmulticast: %i\n"
62                     "\tcached: %i\n",
63                     host_name, port, straddr,
64                     strtxt,
65                     avahi_string_list_get_service_cookie(txt),
66                     !!(flags & AVAHI_LOOKUP_RESULT_LOCAL),
67                     !!(flags & AVAHI_LOOKUP_RESULT_WIDE_AREA),
68                     !!(flags & AVAHI_LOOKUP_RESULT_MULTICAST),
69                     !!(flags & AVAHI_LOOKUP_RESULT_CACHED));
70             avahi_free(strtxt);
71
72             // retrieve fingerprint
73             AvahiStringList *fgli = avahi_string_list_find(txt, MESHLINK_MDNS_FINGERPRINT_KEY);
74             meshlink_node_t *node = meshlink_get_node(mesh, name);
75
76             fprintf(stderr, "%p, %p, %s, %s\n", fgli, node, avahi_string_list_get_text(fgli), meshlink_get_fingerprint(mesh, node));
77
78             if( node && fgli && strcmp(avahi_string_list_get_text(fgli)+strlen(MESHLINK_MDNS_FINGERPRINT_KEY)+1, meshlink_get_fingerprint(mesh, node)) == 0 )
79             {
80                 fprintf(stderr, "Node %s is part of the mesh network - updating ip address.\n", node->name);
81
82                 struct sockaddr_storage naddr;
83                 memset(&naddr, 0, sizeof(naddr));
84
85                 switch(address->proto)
86                 {
87                     case AVAHI_PROTO_INET:
88                     {
89                         struct sockaddr_in* naddr_in = (struct sockaddr_in*)&naddr;
90                         naddr_in->sin_family = AF_INET;
91                         naddr_in->sin_port = port;
92                         naddr_in->sin_addr.s_addr = address->data.ipv4.address;
93                     }
94                     break;
95
96                     case AVAHI_PROTO_INET6:
97                     {
98                         struct sockaddr_in6* naddr_in = (struct sockaddr_in6*)&naddr;
99                         naddr_in->sin6_family = AF_INET6;
100                         naddr_in->sin6_port = port;
101                         memcpy(naddr_in->sin6_addr.s6_addr, address->data.ipv6.address, sizeof(naddr_in->sin6_addr.s6_addr));
102                     }
103                     break;
104
105                     default:
106                     naddr.ss_family = AF_UNKNOWN;
107                 }
108
109                 // @TODO: aquire mutex?
110                 if(naddr.ss_family == AF_INET || naddr.ss_family == AF_INET6)
111                 {
112                     meshlink_hint_address(mesh, node->name, (struct sockaddr*)&naddr);
113                 }
114             }
115             else
116             {
117                 fprintf(stderr, "Node %s is not part of the mesh network - ignoring ip address.\n", node ? node->name : "n/a");
118             }
119         }
120     }
121
122     avahi_s_service_resolver_free(resolver);
123 }
124
125 static void discovery_entry_group_callback(AvahiServer *server, AvahiSEntryGroup *group, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata)
126 {
127     meshlink_handle_t *mesh = userdata;
128
129     /* Called whenever the entry group state changes */
130     switch (state)
131     {
132         case AVAHI_ENTRY_GROUP_ESTABLISHED:
133             /* The entry group has been established successfully */
134             fprintf(stderr, "Service '%s' successfully established.\n", /*MESHLINK_MDNS_SERVICE_NAME*/ mesh->name);
135             break;
136
137         case AVAHI_ENTRY_GROUP_COLLISION:
138             fprintf(stderr, "Service name collision '%s'\n", /*MESHLINK_MDNS_SERVICE_NAME*/ mesh->name);
139             break;
140
141         case AVAHI_ENTRY_GROUP_FAILURE :
142             fprintf(stderr, "Entry group failure: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
143
144             /* Some kind of failure happened while we were registering our services */
145             avahi_simple_poll_quit(mesh->avahi_poll);
146             break;
147
148         case AVAHI_ENTRY_GROUP_UNCOMMITED:
149         case AVAHI_ENTRY_GROUP_REGISTERING:
150             ;
151     }
152 }
153
154
155 static void discovery_create_services(meshlink_handle_t *mesh)
156 {
157     fprintf(stderr, "Adding service '%s'\n", /*MESHLINK_MDNS_SERVICE_NAME*/ mesh->name);
158
159     /* If this is the first time we're called, let's create a new entry group */
160     if (!mesh->avahi_group)
161         if (!(mesh->avahi_group = avahi_s_entry_group_new(mesh->avahi_server, discovery_entry_group_callback, mesh))) {
162             fprintf(stderr, "avahi_entry_group_new() failed: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
163             goto fail;
164         }
165
166     /* Create some random TXT data */
167     char fingerprint[1024] = "";
168     snprintf(fingerprint, sizeof(fingerprint), "%s=%s", MESHLINK_MDNS_FINGERPRINT_KEY, meshlink_get_fingerprint(mesh, meshlink_get_node(mesh, mesh->name)));
169
170     /* Add the service for IPP */
171     int ret = 0;
172     if ((ret = avahi_server_add_service(mesh->avahi_server, mesh->avahi_group, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, 0, /*MESHLINK_MDNS_SERVICE_NAME*/ mesh->name, MESHLINK_MDNS_SERVICE_TYPE, NULL, NULL, mesh->myport ? atoi(mesh->myport) : 655, fingerprint, NULL)) < 0) {
173         fprintf(stderr, "Failed to add _ipp._tcp service: %s\n", avahi_strerror(ret));
174         goto fail;
175     }
176
177     /* Tell the server to register the service */
178     if ((ret = avahi_s_entry_group_commit(mesh->avahi_group)) < 0) {
179         fprintf(stderr, "Failed to commit entry_group: %s\n", avahi_strerror(ret));
180         goto fail;
181     }
182
183     return;
184
185 fail:
186     avahi_simple_poll_quit(mesh->avahi_poll);
187 }
188
189 static void discovery_server_callback(AvahiServer *server, AvahiServerState state, AVAHI_GCC_UNUSED void * userdata)
190 {
191         meshlink_handle_t *mesh = userdata;
192
193     switch (state)
194     {
195         case AVAHI_SERVER_RUNNING:
196             /* The serve has startup successfully and registered its host
197              * name on the network, so it's time to create our services */
198             if (!mesh->avahi_group)
199                 discovery_create_services(mesh);
200             break;
201
202         case AVAHI_SERVER_COLLISION:
203             /* A host name collision happened. Let's do nothing */
204             break;
205
206         case AVAHI_SERVER_REGISTERING:
207                 /* Let's drop our registered services. When the server is back
208              * in AVAHI_SERVER_RUNNING state we will register them
209              * again with the new host name. */
210             //if (mesh->avahi_group)
211             //    avahi_s_entry_group_reset(mesh->avahi_group);
212             break;
213
214         case AVAHI_SERVER_FAILURE:
215             /* Terminate on failure */
216             fprintf(stderr, "Server failure: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
217             avahi_simple_poll_quit(mesh->avahi_poll);
218             break;
219
220         case AVAHI_SERVER_INVALID:
221             break;
222     }
223 }
224
225 static void discovery_browse_callback(
226     AvahiSServiceBrowser *browser,
227     AvahiIfIndex interface,
228     AvahiProtocol protocol,
229     AvahiBrowserEvent event,
230     const char *name,
231     const char *type,
232     const char *domain,
233     AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
234     void* userdata)
235 {
236         meshlink_handle_t *mesh = userdata;
237
238     /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
239
240     switch (event)
241     {
242         case AVAHI_BROWSER_FAILURE:
243             fprintf(stderr, "(Browser) %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
244             avahi_simple_poll_quit(mesh->avahi_poll);
245             return;
246
247         case AVAHI_BROWSER_NEW:
248             fprintf(stderr, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
249             /* We ignore the returned resolver object. In the callback
250                function we free it. If the server is terminated before
251                the callback function is called the server will free
252                the resolver for us. */
253             if (!(avahi_s_service_resolver_new(mesh->avahi_server, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, discovery_resolve_callback, mesh)))
254                 fprintf(stderr, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_server_errno(mesh->avahi_server)));
255             break;
256
257         case AVAHI_BROWSER_REMOVE:
258             fprintf(stderr, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
259             break;
260
261         case AVAHI_BROWSER_ALL_FOR_NOW:
262         case AVAHI_BROWSER_CACHE_EXHAUSTED:
263             fprintf(stderr, "(Browser) %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
264             break;
265     }
266 }
267
268 static void *discovery_loop(void *arg)
269 {
270         meshlink_handle_t *mesh = arg;
271
272     avahi_simple_poll_loop(mesh->avahi_poll);
273
274         return NULL;
275 }
276
277 bool discovery_start(meshlink_handle_t *mesh)
278 {
279     // Allocate discovery loop object
280     if (!(mesh->avahi_poll = avahi_simple_poll_new())) {
281         fprintf(stderr, "Failed to create discovery poll object.\n");
282                 goto fail;
283     }
284
285     // Let's set the host name for this server.
286     AvahiServerConfig config;
287     avahi_server_config_init(&config);
288     config.host_name = avahi_strdup(mesh->name);
289     config.publish_workstation = 0;
290
291     /* Allocate a new server */
292     int error;
293     mesh->avahi_server = avahi_server_new(avahi_simple_poll_get(mesh->avahi_poll), &config, discovery_server_callback, mesh, &error);
294
295     /* Free the configuration data */
296     avahi_server_config_free(&config);
297
298     /* Check wether creating the server object succeeded */
299     if (!mesh->avahi_server) {
300         fprintf(stderr, "Failed to create discovery server: %s\n", avahi_strerror(error));
301         goto fail;
302     }
303
304     // Create the service browser
305     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))) {
306         fprintf(stderr, "Failed to create discovery service browser: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
307         goto fail;
308     }
309
310         // Start the discovery thread
311         if(pthread_create(&mesh->discovery_thread, NULL, discovery_loop, mesh) != 0) {
312                 fprintf(stderr, "Could not start discovery thread: %s\n", strerror(errno));
313                 memset(&mesh->discovery_thread, 0, sizeof mesh->discovery_thread);
314                 goto fail;
315         }
316
317         mesh->discovery_threadstarted = true;
318
319         return true;
320
321 fail:
322     if (mesh->avahi_browser)
323         avahi_s_service_browser_free(mesh->avahi_browser);
324
325     if (mesh->avahi_server)
326         avahi_server_free(mesh->avahi_server);
327
328     if (mesh->avahi_poll)
329         avahi_simple_poll_free(mesh->avahi_poll);
330
331     return false;
332 }
333
334 void discovery_stop(meshlink_handle_t *mesh)
335 {
336         // Shut down 
337         avahi_simple_poll_quit(mesh->avahi_poll);
338
339         // Wait for the discovery thread to finish
340
341         pthread_join(mesh->discovery_thread, NULL);
342
343         // Clean up resources
344     if (mesh->avahi_browser)
345         avahi_s_service_browser_free(mesh->avahi_browser);
346
347     if (mesh->avahi_server)
348         avahi_server_free(mesh->avahi_server);
349
350     if (mesh->avahi_poll)
351         avahi_simple_poll_free(mesh->avahi_poll);
352 }