]> git.meshlink.io Git - meshlink/blob - src/discovery.c
a first mdns discovery implementation
[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                 meshlink_hint_address(mesh, node->name, &naddr);
111             }
112             else
113             {
114                 fprintf(stderr, "Node %s is not part of the mesh network - ignoring ip address.\n", node ? node->name : "n/a");
115             }
116         }
117     }
118
119     avahi_s_service_resolver_free(resolver);
120 }
121
122 static void discovery_entry_group_callback(AvahiServer *server, AvahiSEntryGroup *group, AvahiEntryGroupState state, AVAHI_GCC_UNUSED void *userdata)
123 {
124     meshlink_handle_t *mesh = userdata;
125
126     /* Called whenever the entry group state changes */
127     switch (state)
128     {
129         case AVAHI_ENTRY_GROUP_ESTABLISHED:
130             /* The entry group has been established successfully */
131             fprintf(stderr, "Service '%s' successfully established.\n", /*MESHLINK_MDNS_SERVICE_NAME*/ mesh->name);
132             break;
133
134         case AVAHI_ENTRY_GROUP_COLLISION:
135             fprintf(stderr, "Service name collision '%s'\n", /*MESHLINK_MDNS_SERVICE_NAME*/ mesh->name);
136             break;
137
138         case AVAHI_ENTRY_GROUP_FAILURE :
139             fprintf(stderr, "Entry group failure: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
140
141             /* Some kind of failure happened while we were registering our services */
142             avahi_simple_poll_quit(mesh->avahi_poll);
143             break;
144
145         case AVAHI_ENTRY_GROUP_UNCOMMITED:
146         case AVAHI_ENTRY_GROUP_REGISTERING:
147             ;
148     }
149 }
150
151
152 static void discovery_create_services(meshlink_handle_t *mesh)
153 {
154     fprintf(stderr, "Adding service '%s'\n", /*MESHLINK_MDNS_SERVICE_NAME*/ mesh->name);
155
156     /* If this is the first time we're called, let's create a new entry group */
157     if (!mesh->avahi_group)
158         if (!(mesh->avahi_group = avahi_s_entry_group_new(mesh->avahi_server, discovery_entry_group_callback, mesh))) {
159             fprintf(stderr, "avahi_entry_group_new() failed: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
160             goto fail;
161         }
162
163     /* Create some random TXT data */
164     char fingerprint[1024] = "";
165     snprintf(fingerprint, sizeof(fingerprint), "%s=%s", MESHLINK_MDNS_FINGERPRINT_KEY, meshlink_get_fingerprint(mesh, meshlink_get_node(mesh, mesh->name)));
166
167     /* Add the service for IPP */
168     int ret = 0;
169     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) {
170         fprintf(stderr, "Failed to add _ipp._tcp service: %s\n", avahi_strerror(ret));
171         goto fail;
172     }
173
174     /* Tell the server to register the service */
175     if ((ret = avahi_s_entry_group_commit(mesh->avahi_group)) < 0) {
176         fprintf(stderr, "Failed to commit entry_group: %s\n", avahi_strerror(ret));
177         goto fail;
178     }
179
180     return;
181
182 fail:
183     avahi_simple_poll_quit(mesh->avahi_poll);
184 }
185
186 static void discovery_server_callback(AvahiServer *server, AvahiServerState state, AVAHI_GCC_UNUSED void * userdata)
187 {
188         meshlink_handle_t *mesh = userdata;
189
190     switch (state)
191     {
192         case AVAHI_SERVER_RUNNING:
193             /* The serve has startup successfully and registered its host
194              * name on the network, so it's time to create our services */
195             if (!mesh->avahi_group)
196                 discovery_create_services(mesh);
197             break;
198
199         case AVAHI_SERVER_COLLISION:
200             /* A host name collision happened. Let's do nothing */
201             break;
202
203         case AVAHI_SERVER_REGISTERING:
204                 /* Let's drop our registered services. When the server is back
205              * in AVAHI_SERVER_RUNNING state we will register them
206              * again with the new host name. */
207             //if (mesh->avahi_group)
208             //    avahi_s_entry_group_reset(mesh->avahi_group);
209             break;
210
211         case AVAHI_SERVER_FAILURE:
212             /* Terminate on failure */
213             fprintf(stderr, "Server failure: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
214             avahi_simple_poll_quit(mesh->avahi_poll);
215             break;
216
217         case AVAHI_SERVER_INVALID:
218             break;
219     }
220 }
221
222 static void discovery_browse_callback(
223     AvahiSServiceBrowser *browser,
224     AvahiIfIndex interface,
225     AvahiProtocol protocol,
226     AvahiBrowserEvent event,
227     const char *name,
228     const char *type,
229     const char *domain,
230     AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
231     void* userdata)
232 {
233         meshlink_handle_t *mesh = userdata;
234
235     /* Called whenever a new services becomes available on the LAN or is removed from the LAN */
236
237     switch (event)
238     {
239         case AVAHI_BROWSER_FAILURE:
240             fprintf(stderr, "(Browser) %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
241             avahi_simple_poll_quit(mesh->avahi_poll);
242             return;
243
244         case AVAHI_BROWSER_NEW:
245             fprintf(stderr, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
246             /* We ignore the returned resolver object. In the callback
247                function we free it. If the server is terminated before
248                the callback function is called the server will free
249                the resolver for us. */
250             if (!(avahi_s_service_resolver_new(mesh->avahi_server, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, discovery_resolve_callback, mesh)))
251                 fprintf(stderr, "Failed to resolve service '%s': %s\n", name, avahi_strerror(avahi_server_errno(mesh->avahi_server)));
252             break;
253
254         case AVAHI_BROWSER_REMOVE:
255             fprintf(stderr, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
256             break;
257
258         case AVAHI_BROWSER_ALL_FOR_NOW:
259         case AVAHI_BROWSER_CACHE_EXHAUSTED:
260             fprintf(stderr, "(Browser) %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW");
261             break;
262     }
263 }
264
265 static void *discovery_loop(void *arg)
266 {
267         meshlink_handle_t *mesh = arg;
268
269     avahi_simple_poll_loop(mesh->avahi_poll);
270
271         return NULL;
272 }
273
274 bool discovery_start(meshlink_handle_t *mesh)
275 {
276     // Allocate discovery loop object
277     if (!(mesh->avahi_poll = avahi_simple_poll_new())) {
278         fprintf(stderr, "Failed to create discovery poll object.\n");
279                 goto fail;
280     }
281
282     // Let's set the host name for this server.
283     AvahiServerConfig config;
284     avahi_server_config_init(&config);
285     config.host_name = avahi_strdup(mesh->name);
286     config.publish_workstation = 0;
287
288     /* Allocate a new server */
289     int error;
290     mesh->avahi_server = avahi_server_new(avahi_simple_poll_get(mesh->avahi_poll), &config, discovery_server_callback, mesh, &error);
291
292     /* Free the configuration data */
293     avahi_server_config_free(&config);
294
295     /* Check wether creating the server object succeeded */
296     if (!mesh->avahi_server) {
297         fprintf(stderr, "Failed to create discovery server: %s\n", avahi_strerror(error));
298         goto fail;
299     }
300
301     // Create the service browser
302     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))) {
303         fprintf(stderr, "Failed to create discovery service browser: %s\n", avahi_strerror(avahi_server_errno(mesh->avahi_server)));
304         goto fail;
305     }
306
307         // Start the discovery thread
308         if(pthread_create(&mesh->discovery_thread, NULL, discovery_loop, mesh) != 0) {
309                 fprintf(stderr, "Could not start discovery thread: %s\n", strerror(errno));
310                 memset(&mesh->discovery_thread, 0, sizeof mesh->discovery_thread);
311                 goto fail;
312         }
313
314         mesh->discovery_threadstarted = true;
315
316         return true;
317
318 fail:
319     if (mesh->avahi_browser)
320         avahi_s_service_browser_free(mesh->avahi_browser);
321
322     if (mesh->avahi_server)
323         avahi_server_free(mesh->avahi_server);
324
325     if (mesh->avahi_poll)
326         avahi_simple_poll_free(mesh->avahi_poll);
327
328     return false;
329 }
330
331 void discovery_stop(meshlink_handle_t *mesh)
332 {
333         // Shut down 
334         avahi_simple_poll_quit(mesh->avahi_poll);
335
336         // Wait for the discovery thread to finish
337
338         pthread_join(mesh->discovery_thread, NULL);
339
340         // Clean up resources
341     if (mesh->avahi_browser)
342         avahi_s_service_browser_free(mesh->avahi_browser);
343
344     if (mesh->avahi_server)
345         avahi_server_free(mesh->avahi_server);
346
347     if (mesh->avahi_poll)
348         avahi_simple_poll_free(mesh->avahi_poll);
349 }