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