1
/* scan_avahi.c: detect NUT avahi services
3
* Copyright (C) 2011 - Frederic Bohe <fredericbohe@eaton.com>
5
* This program is free software; you can redistribute it and/or modify
6
* it under the terms of the GNU General Public License as published by
7
* the Free Software Foundation; either version 2 of the License, or
8
* (at your option) any later version.
10
* This program is distributed in the hope that it will be useful,
11
* but WITHOUT ANY WARRANTY; without even the implied warranty of
12
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
* GNU General Public License for more details.
15
* You should have received a copy of the GNU General Public License
16
* along with this program; if not, write to the Free Software
17
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29
#include <avahi-client/client.h>
30
#include <avahi-client/lookup.h>
32
#include <avahi-common/simple-watch.h>
33
#include <avahi-common/malloc.h>
34
#include <avahi-common/error.h>
38
/* dynamic link library stuff */
39
static char * libname = "libavahi-client";
40
static lt_dlhandle dl_handle = NULL;
41
static const char *dl_error = NULL;
43
static AvahiClient* (*nut_avahi_service_browser_get_client)(AvahiServiceBrowser *);
44
static int (*nut_avahi_simple_poll_loop)(AvahiSimplePoll *s);
45
static void (*nut_avahi_client_free)(AvahiClient *client);
46
static int (*nut_avahi_client_errno)(AvahiClient*);
47
static void (*nut_avahi_free)(void *p);
48
static void (*nut_avahi_simple_poll_quit)(AvahiSimplePoll *s);
49
static AvahiClient* (*nut_avahi_client_new)(
50
const AvahiPoll *poll_api,
51
AvahiClientFlags flags,
52
AvahiClientCallback callback,
55
static void (*nut_avahi_simple_poll_free)(AvahiSimplePoll *s);
56
static AvahiServiceResolver * (*nut_avahi_service_resolver_new)(
58
AvahiIfIndex interface,
59
AvahiProtocol protocol,
63
AvahiProtocol aprotocol,
64
AvahiLookupFlags flags,
65
AvahiServiceResolverCallback callback,
67
static const char * (*nut_avahi_strerror)(int error);
68
static AvahiClient* (*nut_avahi_service_resolver_get_client)(AvahiServiceResolver *);
69
static AvahiServiceBrowser* (*nut_avahi_service_browser_new)(
71
AvahiIfIndex interface,
72
AvahiProtocol protocol,
75
AvahiLookupFlags flags,
76
AvahiServiceBrowserCallback callback,
78
static int (*nut_avahi_service_resolver_free)(AvahiServiceResolver *r);
79
static AvahiSimplePoll *(*nut_avahi_simple_poll_new)(void);
80
static char* (*nut_avahi_string_list_to_string)(AvahiStringList *l);
81
static int (*nut_avahi_service_browser_free)(AvahiServiceBrowser *);
82
static char * (*nut_avahi_address_snprint)(char *ret_s, size_t length, const AvahiAddress *a);
83
static const AvahiPoll* (*nut_avahi_simple_poll_get)(AvahiSimplePoll *s);
85
/* return 0 on error */
86
int nutscan_load_avahi_library()
88
if( dl_handle != NULL ) {
89
/* if previous init failed */
90
if( dl_handle == (void *)1 ) {
93
/* init has already been done */
97
if( lt_dlinit() != 0 ) {
98
fprintf(stderr, "Error initializing lt_init\n");
102
dl_handle = lt_dlopenext(libname);
104
dl_error = lt_dlerror();
107
lt_dlerror(); /* Clear any existing error */
108
*(void **) (&nut_avahi_service_browser_get_client) = lt_dlsym(dl_handle, "avahi_service_browser_get_client");
109
if ((dl_error = lt_dlerror()) != NULL) {
113
*(void **) (&nut_avahi_simple_poll_loop) = lt_dlsym(dl_handle, "avahi_simple_poll_loop");
114
if ((dl_error = lt_dlerror()) != NULL) {
118
*(void **) (&nut_avahi_client_free) = lt_dlsym(dl_handle, "avahi_client_free");
119
if ((dl_error = lt_dlerror()) != NULL) {
123
*(void **) (&nut_avahi_client_errno) = lt_dlsym(dl_handle, "avahi_client_errno");
124
if ((dl_error = lt_dlerror()) != NULL) {
128
*(void **) (&nut_avahi_free) = lt_dlsym(dl_handle, "avahi_free");
129
if ((dl_error = lt_dlerror()) != NULL) {
133
*(void **) (&nut_avahi_simple_poll_quit) = lt_dlsym(dl_handle, "avahi_simple_poll_quit");
134
if ((dl_error = lt_dlerror()) != NULL) {
138
*(void **) (&nut_avahi_client_new) = lt_dlsym(dl_handle, "avahi_client_new");
139
if ((dl_error = lt_dlerror()) != NULL) {
143
*(void **) (&nut_avahi_simple_poll_free) = lt_dlsym(dl_handle, "avahi_simple_poll_free");
144
if ((dl_error = lt_dlerror()) != NULL) {
148
*(void **) (&nut_avahi_service_resolver_new) = lt_dlsym(dl_handle, "avahi_service_resolver_new");
149
if ((dl_error = lt_dlerror()) != NULL) {
153
*(void **) (&nut_avahi_strerror) = lt_dlsym(dl_handle, "avahi_strerror");
154
if ((dl_error = lt_dlerror()) != NULL) {
158
*(void **) (&nut_avahi_service_resolver_get_client) = lt_dlsym(dl_handle, "avahi_service_resolver_get_client");
159
if ((dl_error = lt_dlerror()) != NULL) {
163
*(void **) (&nut_avahi_service_browser_new) = lt_dlsym(dl_handle, "avahi_service_browser_new");
164
if ((dl_error = lt_dlerror()) != NULL) {
168
*(void **) (&nut_avahi_service_resolver_free) = lt_dlsym(dl_handle, "avahi_service_resolver_free");
169
if ((dl_error = lt_dlerror()) != NULL) {
173
*(void **) (&nut_avahi_simple_poll_new) = lt_dlsym(dl_handle, "avahi_simple_poll_new");
174
if ((dl_error = lt_dlerror()) != NULL) {
178
*(void **) (&nut_avahi_string_list_to_string) = lt_dlsym(dl_handle, "avahi_string_list_to_string");
179
if ((dl_error = lt_dlerror()) != NULL) {
183
*(void **) (&nut_avahi_service_browser_free) = lt_dlsym(dl_handle, "avahi_service_browser_free");
184
if ((dl_error = lt_dlerror()) != NULL) {
188
*(void **) (&nut_avahi_address_snprint) = lt_dlsym(dl_handle, "avahi_address_snprint");
189
if ((dl_error = lt_dlerror()) != NULL) {
193
*(void **) (&nut_avahi_simple_poll_get) = lt_dlsym(dl_handle, "avahi_simple_poll_get");
194
if ((dl_error = lt_dlerror()) != NULL) {
200
fprintf(stderr, "Cannot load AVAHI library (%s) : %s. AVAHI search disabled.\n", libname, dl_error);
202
dl_handle = (void *)1;
206
/* end of dynamic link library stuff */
208
static AvahiSimplePoll *simple_poll = NULL;
209
static nutscan_device_t * dev_ret = NULL;
210
static long avahi_usec_timeout = 0;
212
static void update_device(const char * host_name,const char *ip, uint16_t port,char * text, int proto)
214
nutscan_device_t * dev = NULL;
217
char * t_saveptr = NULL;
218
char * phrase = NULL;
219
char * phrase_saveptr = NULL;
222
char * device = NULL;
223
char * device_saveptr = NULL;
224
int device_found = 0;
233
phrase = strtok_r(t,"\"",&t_saveptr);
234
while(phrase != NULL ) {
235
word = strtok_r(phrase,"=",&phrase_saveptr);
237
phrase = strtok_r(NULL,"\"",&t_saveptr);
240
value = strtok_r(NULL,"=",&phrase_saveptr);
241
if( value == NULL ) {
242
phrase = strtok_r(NULL,"\"",&t_saveptr);
246
if( strcmp(word,"device_list") != 0 ) {
247
phrase = strtok_r(NULL,"\"",&t_saveptr);
251
device = strtok_r(value,";",&device_saveptr);
252
while( device != NULL ) {
254
dev = nutscan_new_device();
255
dev->type = TYPE_NUT;
256
dev->driver = strdup("nutclient");
257
if( proto == AVAHI_PROTO_INET) {
258
nutscan_add_option_to_device(dev,"desc","IPv4");
260
if( proto == AVAHI_PROTO_INET6 ) {
261
nutscan_add_option_to_device(dev,"desc","IPv6");
266
- port number (max 65535 so 5 characters),
267
- '@' and ':' characters
269
buf_size = strlen(device)+strlen(host_name)+
271
dev->port=malloc(buf_size);
273
snprintf(dev->port,buf_size,"%s@%s:%u",
274
device,host_name,port);
278
/*+1+1 is for '@' character and terminating 0 */
279
buf_size = strlen(device)+strlen(host_name)+1+1;
280
dev->port=malloc(buf_size);
282
snprintf(dev->port,buf_size,"%s@%s",
287
dev_ret = nutscan_add_device_to_device(dev_ret,dev);
290
nutscan_free_device(dev);
292
device = strtok_r(NULL,";",&device_saveptr);
295
phrase = strtok_r(NULL,"\"",&t_saveptr);
299
/* If no device published in avahi data, try to get the device by
300
connecting directly to upsd */
302
snprintf(buf,sizeof(buf),"%u",port);
303
dev = nutscan_scan_nut(ip,ip,buf,avahi_usec_timeout);
305
dev_ret = nutscan_add_device_to_device(dev_ret,dev);
307
/* add an upsd entry without associated device */
309
dev = nutscan_new_device();
310
dev->type = TYPE_NUT;
311
dev->driver = strdup("nutclient");
312
if( proto == AVAHI_PROTO_INET) {
313
nutscan_add_option_to_device(dev,"desc","IPv4");
315
if( proto == AVAHI_PROTO_INET6 ) {
316
nutscan_add_option_to_device(dev,"desc","IPv6");
319
/*+1+1 is for ':' character and terminating 0 */
320
/*buf is the string containing the port number*/
321
buf_size = strlen(host_name)+strlen(buf)+1+1;
322
dev->port=malloc(buf_size);
324
snprintf(dev->port,buf_size,"%s:%s",
329
dev->port=strdup(host_name);
332
dev_ret = nutscan_add_device_to_device(dev_ret,dev);
335
nutscan_free_device(dev);
341
static void resolve_callback(
342
AvahiServiceResolver *r,
343
AVAHI_GCC_UNUSED AvahiIfIndex interface,
344
AVAHI_GCC_UNUSED AvahiProtocol protocol,
345
AvahiResolverEvent event,
349
const char *host_name,
350
const AvahiAddress *address,
352
AvahiStringList *txt,
353
AvahiLookupResultFlags flags,
354
AVAHI_GCC_UNUSED void* userdata) {
358
/* Called whenever a service has been resolved successfully or timed out */
361
case AVAHI_RESOLVER_FAILURE:
362
fprintf(stderr, "(Resolver) Failed to resolve service '%s' of type '%s' in domain '%s': %s\n", name, type, domain, (*nut_avahi_strerror)((*nut_avahi_client_errno)((*nut_avahi_service_resolver_get_client)(r))));
365
case AVAHI_RESOLVER_FOUND: {
366
char a[AVAHI_ADDRESS_STR_MAX], *t;
368
/* fprintf(stderr, "Service '%s' of type '%s' in domain '%s':\n", name, type, domain); */
370
(*nut_avahi_address_snprint)(a, sizeof(a), address);
371
t = (*nut_avahi_string_list_to_string)(txt);
384
avahi_string_list_get_service_cookie(txt),
385
!!(flags & AVAHI_LOOKUP_RESULT_LOCAL),
386
!!(flags & AVAHI_LOOKUP_RESULT_OUR_OWN),
387
!!(flags & AVAHI_LOOKUP_RESULT_WIDE_AREA),
388
!!(flags & AVAHI_LOOKUP_RESULT_MULTICAST),
389
!!(flags & AVAHI_LOOKUP_RESULT_CACHED));
391
update_device(host_name,a,port,t,address->proto);
392
(*nut_avahi_free)(t);
396
(*nut_avahi_service_resolver_free)(r);
399
static void browse_callback(
400
AvahiServiceBrowser *b,
401
AvahiIfIndex interface,
402
AvahiProtocol protocol,
403
AvahiBrowserEvent event,
407
AVAHI_GCC_UNUSED AvahiLookupResultFlags flags,
410
AvahiClient *c = userdata;
413
/* Called whenever a new services becomes available on the LAN or is removed from the LAN */
416
case AVAHI_BROWSER_FAILURE:
418
fprintf(stderr, "(Browser) %s\n", (*nut_avahi_strerror)((*nut_avahi_client_errno)((*nut_avahi_service_browser_get_client)(b))));
419
(*nut_avahi_simple_poll_quit)(simple_poll);
422
case AVAHI_BROWSER_NEW:
423
/* fprintf(stderr, "(Browser) NEW: service '%s' of type '%s' in domain '%s'\n", name, type, domain); */
425
/* We ignore the returned resolver object. In the callback
426
function we free it. If the server is terminated before
427
the callback function is called the server will free
428
the resolver for us. */
430
if (!((*nut_avahi_service_resolver_new)(c, interface, protocol, name, type, domain, AVAHI_PROTO_UNSPEC, 0, resolve_callback, c)))
431
fprintf(stderr, "Failed to resolve service '%s': %s\n", name, (*nut_avahi_strerror)((*nut_avahi_client_errno)(c)));
435
case AVAHI_BROWSER_REMOVE:
436
fprintf(stderr, "(Browser) REMOVE: service '%s' of type '%s' in domain '%s'\n", name, type, domain);
439
case AVAHI_BROWSER_ALL_FOR_NOW:
440
(*nut_avahi_simple_poll_quit)(simple_poll);
441
case AVAHI_BROWSER_CACHE_EXHAUSTED:
442
/* fprintf(stderr, "(Browser) %s\n", event == AVAHI_BROWSER_CACHE_EXHAUSTED ? "CACHE_EXHAUSTED" : "ALL_FOR_NOW"); */
447
static void client_callback(AvahiClient *c, AvahiClientState state, AVAHI_GCC_UNUSED void * userdata) {
450
/* Called whenever the client or server state changes */
452
if (state == AVAHI_CLIENT_FAILURE) {
453
fprintf(stderr, "Server connection failure: %s\n", (*nut_avahi_strerror)((*nut_avahi_client_errno)(c)));
454
(*nut_avahi_simple_poll_quit)(simple_poll);
458
nutscan_device_t * nutscan_scan_avahi(long usec_timeout)
460
/* Example service publication
461
* $ avahi-publish -s nut _upsd._tcp 3493 txtvers=1 protovers=1.0.0 device_list="dev1;dev2"
463
AvahiClient *client = NULL;
464
AvahiServiceBrowser *sb = NULL;
467
if( !nutscan_avail_avahi ) {
471
avahi_usec_timeout = usec_timeout;
473
/* Allocate main loop object */
474
if (!(simple_poll = (*nut_avahi_simple_poll_new)())) {
475
fprintf(stderr, "Failed to create simple poll object.\n");
479
/* Allocate a new client */
480
client = (*nut_avahi_client_new)((*nut_avahi_simple_poll_get)(simple_poll), 0, client_callback, NULL, &error);
482
/* Check wether creating the client object succeeded */
484
fprintf(stderr, "Failed to create client: %s\n", (*nut_avahi_strerror)(error));
488
/* Create the service browser */
489
if (!(sb = (*nut_avahi_service_browser_new)(client, AVAHI_IF_UNSPEC, AVAHI_PROTO_UNSPEC, "_upsd._tcp", NULL, 0, browse_callback, client))) {
490
fprintf(stderr, "Failed to create service browser: %s\n", (*nut_avahi_strerror)((*nut_avahi_client_errno)(client)));
494
/* Run the main loop */
495
(*nut_avahi_simple_poll_loop)(simple_poll);
501
(*nut_avahi_service_browser_free)(sb);
504
(*nut_avahi_client_free)(client);
507
(*nut_avahi_simple_poll_free)(simple_poll);
509
return nutscan_rewind_device(dev_ret);
511
#else /* WITH_AVAHI */
513
nutscan_device_t * nutscan_scan_avahi(long usec_timeout)
517
#endif /* WITH_AVAHI */