This document describes Guile-Avahi version 0.4.1. It was last updated in December 2010.
Guile-Avahi provides GNU Guile bindings to the Avahi library. In other words, it makes it possible to write Scheme programs that use the facilities of Avahi. Avahi itself is a C library that implements the multicast DNS (mDNS) and DNS Service Discovery (DNS-SD) protocols, sometimes erroneously referred to as “Bonjour”. Together, these protocols provide support for fully decentralized host naming and service publication and discovery. They are key components of the so-called Zero-Configuration Networking Stack (Zeroconf).
More precisely, Guile-Avahi provides bindings for the client library of Avahi. The client library allows application to use service discovery by transparently connecting them to the Avahi system-wide daemon using D-Bus. This daemon actually implements the DNS-SD protocol and handles service discovery and publication on behalf of applications running on the same host.
Thus, the functionality of Guile-Avahi could be provided to Guile Scheme applications by writing a D-Bus client to the Avahi daemon in Scheme. Alas, no Scheme-friendly D-Bus implementation was available at the time Guile-Avahi was started, hence the approach taken by Guile-Avahi.
This document describes the Scheme API to Avahi offered by Guile-Avahi. The reader is assumed to have basic knowledge of the protocol and library. Please send bug reports and comments to the Guile-Avahi mailing list.
This chapter details the conventions used in Guile-Avahi, as well as specificities of the mapping of the C API to Scheme.
Lots of enumerates and constants are used in the Avahi C API. For each C enumerate type, a disjoint Scheme type is used—thus, enumerate values and constants are not represented by Scheme symbols nor by integers. This makes it impossible to use an enumerate value of the wrong type on the Scheme side: such errors are automatically detected by type-checking.
The enumerate values are bound to variables exported by the
(avahi)
and other modules within the avahi
hierarchy.
These variables are named according to the following convention:
_
character used in the C API is replaced by hyphen -
.
/
character.
Consider for instance this C-side enumerate:
typedef enum { AVAHI_CLIENT_S_REGISTERING, AVAHI_CLIENT_S_RUNNING, AVAHI_CLIENT_S_COLLISION, AVAHI_CLIENT_FAILURE, AVAHI_CLIENT_CONNECTING } AvahiClientState;
The corresponding Scheme values are bound to the following variables
exported by the (avahi client)
module:
client-state/s-registering client-state/s-running client-state/s-collision client-state/failure client-state/connecting
Hopefully, most variable names can be deduced from this convention.
Scheme-side “enumerate” values can be compared using eq?
(see equality predicates in The GNU Guile Reference
Manual). Consider the following example:
(let ((client (make-client ...))) ;; ;; ... ;; ;; Check the client state. (if (eq? (client-state client) client-state/failure) (format #t "Oh, we failed.")))
In addition, all enumerate values can be converted to a human-readable
string, in a type-specific way. For instance,
(watch-event->string watch-event/in)
yields "in"
. Note
that these strings may not be sufficient for use in a user interface
since they are fairly concise and not internationalized.
Unlike C functions in Avahi, the corresponding Scheme procedures are
named in a way that is close to natural English. Abbreviations are
also avoided. For instance, the Scheme procedure corresponding to
avahi_client_get_version
is named
client-server-version
. The avahi_
prefix is always omitted from variable names since a similar effect
can be achieved using Guile’s nifty binding renaming facilities,
should it be needed (see Using Guile Modules in The GNU
Guile Reference Manual).
Except for client
objects, all objects created by the Avahi
client API are local representative of objects implemented by the
system-wide Avahi daemon. For instance, make-service-browser
returns a “service browser” object which is actually a proxy to a
daemon-implemented service browser (see Service Browsing). In
other words, the Avahi daemon allocates resources (objects) on behalf
of its clients.
While the Avahi daemon can reclaim resources allocated on behalf of a client program when that program exits, it cannot automatically determine when such resources become unneeded and reclaimable while the program is running. Thus, clients must explicitly tell the daemon when an object is no longer needed.
Consequently, except for client
objects, objects manipulated by
Guile-Avahi programs must be freed using the appropriate free
procedure. For instance, objects created by
make-service-browser
must eventually be freed by
free-service-browser!
. Additional procedures are available to
determine whether a particular object has already been freed; for
instance, freed-service-browser?
returns #t
when the
given service browser has already been freed. Of course, freed
objects are no longer usable; procedures that are passed a previously
freed object will raise an error/invalid-object
exception
(see Error Handling).
Note that all such client-side proxy objects are not subject to garbage collection until they have been explicitly freed. Therefore, it is important to free them when they are no longer needed!
As an exception, client
objects as returned by
make-client
are subject to garbage collection and need not be
explicitly freed. This is because client programs will usually create
only one client object whose lifetime is that of the program itself.
Avahi errors are implemented as Scheme exceptions (see exceptions in Guile in The GNU Guile Reference Manual). Each
time a Avahi function returns an error, an exception with key
avahi-error
is raised. The additional arguments that are
thrown include an error code and the name of the Avahi procedure that
raised the exception. The error code is pretty much like an enumerate
value: it is one of the error/
variables exported by the
(avahi)
module (see Enumerates and Constants). Exceptions
can be turned into error messages using the error->string
procedure.
The following examples illustrates how Avahi exceptions can be handled:
(let ((poll (make-simple-poll))) ;; ;; ... ;; (catch 'avahi-error (lambda () (run-simple-poll (simple-poll poll))) (lambda (key err function . currently-unused) (format (current-error-port) "an Avahi error was raised by `~a': ~a~%" function (error->string err)))))
Again, error values can be compared using eq?
:
;; `avahi-error' handler. (lambda (key err function . currently-unused) (if (eq? err error/no-daemon) (format (current-error-port) "~a: the Avahi daemon is not running~%" function)))
Note that the catch
handler is currently passed only 3
arguments but future versions might provide it with additional
arguments. Thus, it must be prepared to handle more than 3 arguments,
as in this example.
This chapter lists examples that illustrate common use cases.
The following example shows the simplest way to publish a service. There are several stages:
make-client
. This will
actually connect the application to the Avahi daemon that will
eventually perform operations on behalf of the application.
client-state/s-running
), create an entry group with
make-entry-group
and add a service (or several services,
addresses, etc.) to it.
commit-entry-group
.
entry-group-state/established
state. The application must keep
running so that the service remains published.
Here is the complete example:
(use-modules (avahi) (avahi client) (avahi client publish)) (define (group-callback group state) (if (eq? state entry-group-state/established) (format #t "service is now published!~%"))) (define client-callback (let ((group #f)) (lambda (client state) (if (eq? state client-state/s-running) (begin ;; The client is now running so we can create an entry ;; group and publish a service. (set! group (make-entry-group client group-callback)) (add-entry-group-service! group interface/unspecified protocol/unspecified '() "my-avahi-service" "_some-service-type._tcp" #f #f ;; any domain and host 1234 ;; the port number ;; additional `txt' properties "scheme=yes" "java=no") ;; Commit the entry group, i.e., actually publish ;; the service. (commit-entry-group group)))))) ;; The main loop. (let* ((poll (make-simple-poll)) (client (make-client (simple-poll poll) '() ;; no flags client-callback))) (and (client? client) ;; Run forever. (run-simple-poll poll)))
Of course, publishing a host address or service subtype works similarly.
Browsing advertised services requires a number of stages. First, an Avahi daemon client must be created, as usual (see Publishing a Service).
(use-modules (avahi) (avahi client) (avahi client lookup)) (define %service-type ;; The type of services we are looking for. "_workstation._tcp") (define (service-browser-callback browser interface protocol event service-name service-type domain flags) (if (eq? event browser-event/new) (format #t "found service `~a' of type `~a'~%" service-name service-type))) (define client-callback (let ((browser #f)) (lambda (client state) (if (eq? state client-state/s-running) ;; Now that the client is up and running, create a service ;; browser looking for services of type `%service-type' on ;; any network interface and using any protocol. (set! browser (make-service-browser client interface/unspecified protocol/unspecified %service-type #f '() service-browser-callback)))))) (let* ((poll (make-simple-poll)) (client (make-client (simple-poll poll) '() ;; no flags client-callback))) (and (client? client) (run-simple-poll poll)))
In this example, the service type being looked for is
"_workstation._tcp"
. It is used to advertise the presence of
computers on a local area network, rather than an actual service.
The previous example allowed us to find services of a given type, but did not provide us with information such as the IP address of the host providing the service and the port number where the service can be found. To obtain this information, a service resolver must be launched, e.g., by augmenting the service browser call-back as follows:
(define (service-browser-callback browser interface protocol event service-name service-type domain flags) (define (service-resolver-callback resolver interface protocol event service-name service-type domain host-name address-type address port txt flags) ;; Handle service resolution events. (cond ((eq? event resolver-event/found) (format #t "resolved service `~a' at `~a:~a'~%" service-name host-name port)) ((eq? event resolver-event/failure) (format #t "failed to resolve service `~a'~%" service-name)))) (if (eq? event browser-event/new) (begin (format #t "found service `~a' of type `~a'~%" service-name service-type) ;; Launch a service resolver for the service we just found. (make-service-resolver (service-browser-client browser) interface protocol service-name service-type domain protocol/unspecified '() service-resolver-callback))))
Now you know all the important things you need to know to benefit from Avahi!
This chapter documents Guile-Avahi Scheme procedures. Note that further details can be found in the Avahi C API reference.
This section lists the Scheme procedures exported by the (avahi)
module. These procedures are mainly related to polls, the
building block of event loops in Avahi programs. Polls come in three
flavors:
select()
, processes D-Bus
I/O events, and invokes the relevant client call-backs when appropriate.
Creating and manipulating polls is achieved using the procedures below.
Unlock the event look object associated with threaded-poll.
Lock the event loop associated with threaded-poll. Use this if you want to access the event loop object (e.g., creating a new event source) from anything but the event loop helper thread, i.e. not from callbacks.
Quit the event loop associated with threaded-poll responsible for running the event loop. It must be called from outside said thread (i.e., not from callbacks).
Stop the helper thread associated with threaded-poll responsible for running the event loop. It must be called from outside said thread (i.e., not from callbacks).
Start the helper thread associated with threaded-poll, which is responsible for running the event loop. Callbacks are called from the helper thread. Thus, synchronization may be required among threads.
Return the poll
object associated with threaded-poll.
Return a threaded-poll
object. A threaded poll is essentially an event loop that processes events from the Avahi daemon in its own thread.
Run the event loop of simple-poll until either an error occurs or a quit request is scheduled. In the former case, an error is raised; in the latter, #f
is returned.
Handle events registered by simple-poll. If sleep-time is not specified, the function blocks until an I/O event occurs. If sleep-time is specified, it is the maximum number of milliseconds of blocking. Return #f
is a quit request has been scheduled, #t
otherwise.
Return the poll
object associated with simple-poll.
Return a simple-poll
object. This is the easiest way to handle I/O of Avahi client
objects and similar.
Return the poll
object associated with guile-poll.
Return a guile-poll
object that can then be used to handle I/O events for Avahi objects such as clients. All arguments should be procedures:
watch
object and a list of watch-event
values; new-timeout is passed a timeout object and a number of seconds and nanoseconds representing the absolute date when the timeout expires, or #f
if the newly created timeout is disabled.
watch
object and a new list of events; update-timeout! is passed a new expiration time or #f
.
select
.
The Guile-Avahi source code distribution comes with a detailed example.
Return true if obj is of type timeout
.
Return true if obj is of type watch
.
Return true if obj is of type threaded-poll
.
Return true if obj is of type guile-poll
.
Return true if obj is of type simple-poll
.
Return true if obj is of type poll
.
Return a string describing enumval, a interface
value.
Return a string describing enumval, a protocol
value.
Return a string describing enumval, a watch-event
value.
Return a string describing enumval, a error
value.
The low-level API for watches, timeouts, and “guile polls”, all of
which serve as the basic for the creation of customized event loops
(using make-guile-poll
) is described below. In practice, you
should only need it in applications where the Avahi event loop needs to
be integrated in some other event loop; in other cases, the “simple
poll” or “threaded poll” should be enough.
Associated data (an arbitrary Scheme object) with timeout.
Return the user-specified data associated with timeout.
Return the expiration time for timeout as two values: the number of seconds and nanoseconds. If timeout is disabled, both values are #f
.
Associated data (an arbitrary Scheme object) with watch.
Return the user-specified data associated with watch.
Return the events of interest (a list of watch-event/
values) for watch.
Return the file descriptor associated with watch.
Invoke the call-back associated with timeout. This notifies the interested code that the timeout associated with timeout has been reached. The return value is unspecified. An error/invalid-object
error is raised if timeout is disabled or is no longer valid.
Invoke the call-back associated with watch. This notifies the interested code that the events listed in events (a list of watch-event/
values) occurred on the file descriptor associated with watch. The return value is unspecified. An error/invalid-object
error is raised if watch is no longer valid.
This section lists the Scheme procedures exported by the (avahi
client)
module.
Return the state (a client-state/
value) of client.
Return the fully qualified domain name (FQDN) of the server client is connected to.
Return the host name of the server client is connected to.
Return the version (a string) of the server the client is connected to.
Return a new Avahi client. The client will use poll (a poll
object as returned by, e.g., (simple-poll (make-simple-poll))
) for I/O management. In addition, when the client state changes, callback
(a two-argument procedure) will be invoked and passed the client object and a client-state value. flags must be a list of client flags (i.e., client-flag/
values).
Return true if obj is of type client
.
Return a string describing enumval, a client-flag
value.
Return a string describing enumval, a client-state
value.
The flags argument expected by make-client
is a list
containing zero or more values among the following:
Don’t read user configuration.
Don’t fail if the daemon is not available when make-client
is
called; instead enter client-state/connecting
state and wait
for the daemon to appear.
The service publication API is provided by the (avahi client
publish)
. To publish services, one must first create a client for
the Avahi daemon (see Client Interface).
Find an alternative name to service-name. If called with an original service name, " #2"
is appended. Afterwards the number is incremented on each call (i.e., "foo"
becomes "foo #2"
, which becomes "foo #3"
, and so on).
Find an alternative name to hostname. If called with an original host name, "2"
is appended. Afterwards the number is incremented on each call (i.e., "foo"
becomes "foo2"
, which becomes "foo3"
, and so on).
Add to group a mapping from fully-qualified domain name fqdn to address address. Depending on address-protocol (a protocol/
value), address should be a 32-bit or 128-bit integer (for IPv4 and IPv6, respectively) in host byte order (see Network Address Conversion in The GNU Guile Reference Manual).
Update the service named service-name in group.
Add subtype as a sub-type of a service already present in group. You may add as many subtypes for a service as you wish.
Add a service of type service-type (e.g., "_http._tcp"
) named service-name to group. port should be an integer telling which port this service is listening on; host can be a string indicating which host it is running on, or #f
to let the daemon decide by itself (recommended). Likewise, domain can be #f
(recommended) or a string indicating the domain where this service is to be registered. Additionaly txt arguments should be string denoting additional txt
properties (e.g., "color-printer=yes"
). Finally, interface and protocol denote, respectively, the network interface and protocol used to publish the service. interface may be interface/unspecified
, in which case the daemon will choose the most appropriate interface, or it can be a string (e.g., "eth0"
), or an integer OS-provided integer index; similarly, protocol may be protocol/unspecified
, in which case the daemon will choose a protocol, or it can be any other protocol/
value.
Return the client used by group.
Return #t
if group is empty, #f
otherwise.
Return the state of group, i.e., an entry-group-state/
value.
Reset group.
Commit entry group group, i.e., register its entries on the network. It is an error to commit an empty group.
Return a new entry group using client and callback as the state-change notification procedure. callback should be a two-argument procedure. It will be passed the group object and the group entry’s state (i.e., a group-entry-state/
value).
Return a string describing enumval, a publish-flag
value.
Return a string describing enumval, a entry-group-state
value.
Return true if obj is of type entry-group
.
Return #t
if obj is an object of type entry-group
that has already been explicitly freed.
Explicitly free obj, an object of type entry-group
.
The publish-flags argument expected by
add-entry-group-service!
and similar procedures is a list
containing zero or more values among the following:
For raw records: The RRset is intended to be unique.
For raw records: Though the RRset is intended to be unique no probes shall be sent.
For raw records: Do not announce this RR to other hosts.
For raw records: Allow multiple local records of this type, even if they are intended to be unique.
For address records: don’t create a reverse (PTR) entry.
For service records: do not implicitly add the local service cookie to TXT data.
Update existing records instead of adding new ones.
Register the record using wide area DNS (i.e., unicast DNS update).
Register the record using multicast DNS.
The service discovery API is provided by the (avahi client
lookup)
module. Service discovery typically consists of two phases:
browsing where one can find, e.g., available services, and
resolution where one can, e.g., get detailed information about a
discovered service such as its IP address.
All browsers and resolvers support the following lookup flags:
Force lookup via wide-area DNS.
Force lookup via multicast DNS.
When doing service resolving, don’t lookup TXT record.
When doing service resolving, don’t lookup A/AAAA record.
Procedures to create browsers and resolvers are described below.
Return a new address resolver using the specified client, interface, etc., that will resolve the host name corresponding to address of type address-type (either protocol/inet
for an IPv4 address or protocol/inet6
for an IPv6 address). As usual, address should be the raw IP address in host byte order (see Network Address Conversion in The GNU Guile Reference Manual). Upon resolution, callback is invoked and passed:
protocol/
values);
resolver-event/
values);
lookup-result-flag/
values).
An exception may be raised on failure.
Return a new host-name resolver using the specified client, interface, etc., that will resolve host-name, i.e., find the corresponding IP address. Upon resolution, callback is invoked and passed:
protocol/
values);
resolver-event/
values);
protocol/inet
for an IPv4 address and protocol/inet6
for an IPv6 address);
lookup-result-flag/
values).
An exception may be raised on failure.
Return a new service resolver using the specified client, interface, etc., that will resolve the host name, IP address, port and txt
properties of the service of type type named service-name. Upon resolution, callback is invoked and passed:
protocol/
values);
resolver-event/
values);
"_http._tcp"
);
protocol/inet
for an IPv4 address and protocol/inet6
for an IPv6 address);
txt
properties (strings);
lookup-result-flag/
values).
An exception may be raised on failure.
Return a new service browser using the specified client, interface, etc. Upon browsing events (discovery, removal, etc.) callback will be called and passed:
protocol/
values);
browser-event/
values);
"_http._tcp"
);
lookup-result-flag/
values).
Return a new service type browser using the specified client, interface, etc. Upon browsing events (discovery, removal, etc.) callback will be called and passed:
protocol/
values);
browser-event/
values);
"_http._tcp"
);
lookup-result-flag/
values).
Return a new domain browser of type domain-browser-type (a domain-browser-type/
value) for domain that uses client. Upon browsing events (discovery, removal, etc.) callback will be called and passed:
protocol/
values);
browser-event/
values);
lookup-result-flag/
values).
Return the client associated with address-resolver.
Return the client associated with host-name-resolver.
Return the client associated with service-resolver.
Return the client associated with service-browser.
Return the client associated with service-type-browser.
Return the client associated with domain-browser.
Return a string describing enumval, a lookup-result-flag
value.
Return a string describing enumval, a lookup-flag
value.
Return a string describing enumval, a resolver-event
value.
Return a string describing enumval, a browser-event
value.
Return a string describing enumval, a domain-browser-type
value.
Return true if obj is of type address-resolver
.
Return #t
if obj is an object of type address-resolver
that has already been explicitly freed.
Explicitly free obj, an object of type address-resolver
.
Return true if obj is of type host-name-resolver
.
Return #t
if obj is an object of type host-name-resolver
that has already been explicitly freed.
Explicitly free obj, an object of type host-name-resolver
.
Return true if obj is of type service-resolver
.
Return #t
if obj is an object of type service-resolver
that has already been explicitly freed.
Explicitly free obj, an object of type service-resolver
.
Return true if obj is of type service-type-browser
.
Return #t
if obj is an object of type service-type-browser
that has already been explicitly freed.
Explicitly free obj, an object of type service-type-browser
.
Return true if obj is of type service-browser
.
Return #t
if obj is an object of type service-browser
that has already been explicitly freed.
Explicitly free obj, an object of type service-browser
.
Return true if obj is of type domain-browser
.
Return #t
if obj is an object of type domain-browser
that has already been explicitly freed.
Explicitly free obj, an object of type domain-browser
.
Browser and resolver call-backs are usually passed a browser event or resolver event value, respectively, among the following:
The object is new on the network.
The object has been removed from the network.
One-time event, to notify the user that all entries from the caches have been sent.
One-time event, to notify the user that more records will probably not show up in the near future, i.e., all cache entries have been read and all static servers been queried.
Browsing failed.
RR found, resolving successful.
Resolving failed.
In addition, browser and resolver call-backs are passed a list lookup result flags which is a list of values among the following:
This response originates from the cache.
This response originates from wide area DNS.
This response originates from multicast DNS.
This record/service resides on and was announced by the local
host. Only available in service and record browsers and only on
browser-event/new
events.
This service belongs to the same local client as the browser object.
Only available for service browsers and only on
browser-event/new
events.
This is useful for applications that both publish and browse services to distinguish between services published by the application itself and services published from other applications.
The returned data has been defined statically by some configuration option.
Jump to: | A B C D E M P R Z |
---|
Jump to: | A B C D E M P R Z |
---|
Jump to: | A B C D E F G H I L M P Q R S T U W |
---|
Jump to: | A B C D E F G H I L M P Q R S T U W |
---|
Jump to: | B C E L P R W |
---|
Jump to: | B C E L P R W |
---|