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